com.proteus.jsmodify.JSExecutionTracer.java Source code

Java tutorial

Introduction

Here is the source code for com.proteus.jsmodify.JSExecutionTracer.java

Source

/*
 * Automatic JavaScript Invariants is a plugin for Crawljax that can be used to derive JavaScript
 * invariants automatically and use them for regressions testing. Copyright (C) 2010 crawljax.com
 * This program is free software: you can redistribute it and/or modify it under the terms of the
 * GNU General Public License as published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version. This program is distributed in the hope that it
 * will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should
 * have received a copy of the GNU General Public License along with this program. If not, see
 * <http://www.gnu.org/licenses/>.
 */
package com.proteus.jsmodify;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.PrintStream;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.logging.Logger;

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
/**
import com.clematis.core.episode.Episode;
import com.clematis.core.episode.Story;
**/
/**
import com.clematis.visual.EpisodeGraph;
import com.clematis.visual.JSUml2Story;
**/
import com.crawljax.util.Helper;
import com.fasterxml.jackson.annotation.JsonAutoDetect.Visibility;
import com.fasterxml.jackson.core.JsonGenerationException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.databind.introspect.VisibilityChecker;
import com.fasterxml.jackson.datatype.guava.GuavaModule;
import com.google.common.collect.Iterables;
import com.google.common.collect.Multimap;
import com.google.common.collect.TreeMultimap;
import com.proteus.core.interactiongraph.InteractionGraph;
import com.proteus.core.staticanalysis.CallGraphAnalyzer;
import com.proteus.core.staticanalysis.StaticAnalyzer;
import com.proteus.core.staticanalysis.TestWala;
import com.proteus.core.trace.DOMEventTrace;
import com.proteus.core.trace.TimingTrace;
import com.proteus.core.trace.TraceObject;
import com.proteus.jsmodify.JSModifyProxyPlugin;

/*****
import com.ibm.wala.ipa.callgraph.CGNode;
import com.ibm.wala.util.graph.Graph;
*****/

/**
 * Reads an instrumentation array from the webbrowser and saves the contents in a JSON trace file.
 * 
 * @author Frank Groeneveld
 * @version $Id: JSExecutionTracer.java 6162 2009-12-16 13:56:21Z frank $
 */
public class JSExecutionTracer {

    private static final int ONE_SEC = 1000;

    private static String outputFolder;
    private static String traceFilename;

    private static JSONArray points = new JSONArray();

    private static final Logger LOGGER = Logger.getLogger(JSExecutionTracer.class.getName());

    public static final String FUNCTIONTRACEDIRECTORY = "functiontrace/";

    private static PrintStream output;

    // private Trace trace;
    /**    private static Story story;
    **/
    private static ObjectMapper mapper = new ObjectMapper();
    static String theTime;
    private static int counter = 0;

    // private ArrayList<TraceObject> sortedTraceList;
    // private ArrayList<Episode> episodeList;

    /**
     * @param filename
     */
    public JSExecutionTracer(String filename) {
        traceFilename = filename;
    }

    /**
     * Initialize the plugin and create folders if needed.
     * 
     * @param browser
     *            The browser.
     */
    public static void preCrawling(ArrayList<String> htmlPaths, ArrayList<String> jsPaths,
            ArrayList<String> jsFileNames) {

        // TODO TODO TODO TODO TODO TODO 
        // TODO TODO TODO TODO TODO TODO
        /*
        if (jsPaths.size() != jsFileNames.size()) {
           System.err.println("ERROR: JSEXECUTIONTRACER::PRECRAWLER -> not equal number of js paths and js file names");
           // return;
        }
        else {
           StaticAnalyzer staticAnalyzer = new StaticAnalyzer();
           for (String htmlPath : htmlPaths) {
         staticAnalyzer.getCallGraph(htmlPath, "");
           }
           for (int i = 0; i < jsFileNames.size(); i ++) {
         staticAnalyzer.getCallGraph(jsFileNames.get(i), jsPaths.get(i));
           }
        }
        */
        // TODO TODO TODO TODO TODO TODO 
        // TODO TODO TODO TODO TODO TODO 

        try {
            points = new JSONArray();

            Helper.directoryCheck(getOutputFolder());
            output = new PrintStream(getOutputFolder() + getFilename());

            // Add opening bracket around whole trace
            PrintStream oldOut = System.out;
            System.setOut(output);
            System.out.println("{");
            System.setOut(oldOut);

        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * Retrieves the JavaScript instrumentation array from the webbrowser and writes its contents in
     * Daikon format to a file.
     * 
     * @param session
     *            The crawling session.
     * @param candidateElements
     *            The candidate clickable elements.
     */

    public void preStateCrawling() {

        String filename = getOutputFolder() + FUNCTIONTRACEDIRECTORY + "jstrace-";

        DateFormat dateFormat = new SimpleDateFormat("yyyyMMddHHmmss");
        Date date = new Date();
        filename += dateFormat.format(date) + ".dtrace";

        try {

            LOGGER.info("Reading execution trace");

            LOGGER.info("Parsing JavaScript execution trace");

            // session.getBrowser().executeJavaScript("sendReally();");
            Thread.sleep(ONE_SEC);

            LOGGER.info("Saved execution trace as " + filename);

        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * Wrtie the story object to a JSON file on disk.
     */
    /**
    public static void writeStoryToDisk() {
        
    mapper.setVisibilityChecker(VisibilityChecker.Std.defaultInstance().withFieldVisibility(
            Visibility.ANY));
    mapper.disable(SerializationFeature.FAIL_ON_EMPTY_BEANS);
    mapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
    // to allow coercion of JSON empty String ("") to null Object value:
    mapper.enable(DeserializationFeature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT);
    mapper.enable(SerializationFeature.INDENT_OUTPUT);
        
    try {
        
        Helper.directoryCheck(Helper.addFolderSlashIfNeeded("captured_stories"));
        mapper.writeValue(new File("captured_stories/story" + ".json"),
                story);
        
    } catch (JsonGenerationException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    } catch (JsonMappingException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    } catch (IOException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    }
        
    }
    **/
    /**
     * Get a list with all trace files in the executiontracedirectory.
     * 
     * @return The list.
     */
    public List<String> allTraceFiles() {
        ArrayList<String> result = new ArrayList<String>();

        /* find all trace files in the trace directory */
        File dir = new File(getOutputFolder() + FUNCTIONTRACEDIRECTORY);

        String[] files = dir.list();
        if (files == null) {
            return result;
        }
        for (String file : files) {
            if (file.endsWith(".dtrace")) {
                result.add(getOutputFolder() + FUNCTIONTRACEDIRECTORY + file);
            }
        }
        return result;
    }

    public static void postCrawling(CallGraphAnalyzer callGraphAnalyzer) {
        try {
            // Add closing bracket
            PrintStream oldOut = System.out;
            System.setOut(output);
            System.out.println(" ");
            System.out.println("}");
            System.setOut(oldOut);

            /* close the output file */
            output.close();

            //            extraxtTraceObjects();
        } catch (Exception e) {
            e.printStackTrace();
        }

        extraxtTraceObjects(); // TODO

        // TODO
        InteractionGraph.getInstance().handleGraphAfterTermination();

        System.out.println("********************************");
        System.out.println("********************************");

        // TODO TODO TODO TODO
        System.out.println("+++++++++++++++++++++++");
        System.out.println("DYNAMIC");
        System.out.println(InteractionGraph.getInstance().getDynamicFunctions().keySet().toString());
        System.out.println("++++++++++++++++STATIC");
        System.out.println(InteractionGraph.getInstance().getStaticFunctions().keySet().toString());
        System.out.println("+++++++++++++++++++++++");
        // TODO TODO TODO TODO

        Set<String> keys = JSModifyProxyPlugin.JSCodeMultiMap.keySet();
        for (String key : keys) {
            System.out.println("key: " + key);
            System.out.println(JSModifyProxyPlugin.JSCodeMultiMap.get(key).size());

            String fileScript = "";

            Iterator<String> itr = JSModifyProxyPlugin.JSCodeMultiMap.get(key).iterator();
            while (itr.hasNext()) {
                // TODO TODO TODO TODO
                // TODO TODO TODO TODO
                // TODO TODO TODO TODO
                // TODO TODO TODO TODO
                // TODO TODO TODO TODO
                // TODO TODO TODO TODO
                // TODO TODO TODO TODO
                // TODO TODO TODO TODO
                // TODO TODO TODO TODO
                // TODO TODO TODO TODO
                // TODO TODO TODO TODO
                System.out.println("///////////////////////////////////////////////");
                System.out.println("KEY: " + key);
                System.out.println("///////////////////////////////////////////////");
                fileScript = fileScript + " \n " + itr.next();
                //            System.out.println(itr.next());
                //            TestWala.getCallGraph(itr.next());
                /*****
                Graph<CGNode> callGraph = callGraphAnalyzer.getCallGraph(itr.next(), key);
                System.out.println("<><><><> " + callGraph.getNumberOfNodes());
                *****/
                // TODO TODO TODO TODO
                // TODO TODO TODO TODO
                // TODO TODO TODO TODO
                // TODO TODO TODO TODO
                // TODO TODO TODO TODO
                // TODO TODO TODO TODO TODO TODO TODO TODO 
                // TODO TODO TODO TODO TODO TODO TODO TODO 
                // TODO TODO TODO TODO TODO TODO TODO TODO 
                // TODO TODO TODO TODO TODO TODO TODO TODO 
                //////////////            Iteractor<CGNode> nodeItr = PrunedCFG<I, IBasicBlock<I>>
                // TODO TODO TODO TODO TODO TODO TODO TODO 
                // TODO TODO TODO TODO TODO TODO TODO TODO 
                // TODO TODO TODO TODO TODO TODO TODO TODO 
                // TODO TODO TODO TODO TODO TODO TODO TODO 
            }

            if (!fileScript.isEmpty()) {
                //            TestWala.getCallGraph(fileScript);
                ////////////////////// TODO removed this to complete dynamic part
                //////////////////////////////////            TestWala.getCallGraphAndSlicer(fileScript); // TODO TODO TODO
            }

            System.out.println("------------");
            //         System.out.println("values: " + JSModifyProxyPlugin.JSCodeMultiMap.get(key));
        }

    }

    /**
     * This method parses the JSON file containing the trace objects and extracts the objects
     */
    public static void extraxtTraceObjects() {
        try {
            ObjectMapper mapper = new ObjectMapper();
            // Register the module that serializes the Guava Multimap
            mapper.registerModule(new GuavaModule());

            File traceFile = new File("clematis-output/ftrace/function.trace");
            //            File traceFile = new File("function-traces/same-game.function.trace");
            //            File traceFile = new File("function-traces/ghostbusters.function.trace");
            //            File traceFile = new File("function-traces/mojule.function.trace");
            //            File traceFile = new File("function-traces/listo.function.trace");
            //            File traceFile = new File("function-traces/doctored.function.trace");
            //            File traceFile = new File("function-traces/jointlondon.function.trace");
            //            File traceFile = new File("function-traces/607.space.mahjon.function.trace");

            Multimap<String, TraceObject> traceMap = mapper.<Multimap<String, TraceObject>>readValue(traceFile,
                    new TypeReference<TreeMultimap<String, TraceObject>>() {
                    });
            /**
                        Collection<TraceObject> timingTraces = traceMap.get("TimingTrace");
            **/
            Collection<TraceObject> domEventTraces = traceMap.get("DOMEventTrace");
            /**            Collection<TraceObject> XHRTraces = traceMap.get("XHRTrace");
            **/
            Collection<TraceObject> functionTraces = traceMap.get("FunctionTrace");

            // TODO TODO TODO TODO TODO
            InteractionGraph.getInstance().handleDynamicCallGraph(functionTraces);
            InteractionGraph.getInstance().handleDomEvents(domEventTraces);
            // TODO TODO TODO TODO TODO

            ///////            createDynamicCallGraph(functionTraces);

            /**            Iterator<TraceObject> it3 = domEventTraces.iterator();
                        TraceObject next2;
                        ArrayList<TraceObject> removeus = new ArrayList<TraceObject>();
                        while (it3.hasNext()) {
            next2 = it3.next();
                
            if (next2 instanceof DOMEventTrace
                    && (((DOMEventTrace) next2).getEventType().equals("mouseover") 
                            || (((DOMEventTrace) next2).getEventType().equals("mousemove"))
                            || (((DOMEventTrace) next2).getEventType().equals("mouseout"))
                            || (((DOMEventTrace) next2).getEventType().equals("mousedown"))
                            || (((DOMEventTrace) next2).getEventType().equals("mouseup")))) {
                removeus.add(next2);
                
            }
                        }
                        domEventTraces.removeAll(removeus);
                
                        story = new Story(domEventTraces, functionTraces, timingTraces, XHRTraces);
                        story.setOrderedTraceList(sortTraceObjects());
                
                
                        System.out.println(timingTraces.size());
                        Iterator<TraceObject> it = timingTraces.iterator();
                        TraceObject next;
                
                        while (it.hasNext()) {
            next = it.next();
            System.out.println("=======");
            System.out.println( next.getCounter());
                        }
            **/

            /*
             * ArrayList<TraceObject> bookmarkTraceObjects = new ArrayList<TraceObject>(); for
             * (TraceObject to : story.getOrderedTraceList()) { if (to instanceof DOMEventTrace) {
             * if (((DOMEventTrace)to).getEventType().equals("_BOOKMARK_")) {
             * bookmarkTraceObjects.add(to);
             * System.out.println("&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&"); } } }
             */
            /**
                        story.setEpisodes(buildEpisodes());
                
                        ArrayList<Episode> ss = story.getEpisodes();
                        Iterator<Episode> it2 = ss.iterator();
                        System.out.println("hhhmmm");
                
                        while (it2.hasNext()) {
            System.out.println("--------");
            System.out.println(it2.next().getSource().getClass());
                        }
                
                
                
                        System.out.println("# of trace objects: " + story.getOrderedTraceList().size());
                        System.out.println("# of episodes: " + story.getEpisodes().size());
            **/ /*
                 * for (int i = 0; i < story.getEpisodes().size(); i ++) { Episode episode =
                 * story.getEpisodes().get(i); if (episode.getSource() instanceof DOMEventTrace) {
                 * DOMEventTrace source = (DOMEventTrace)episode.getSource();
                 * if(source.getTargetElement().contains("bookmarkButton")) {
                 * System.out.println("***********"); if (i + 1 < story.getEpisodes().size()) {
                 * story.getEpisodes().get(i).getSource().setIsBookmarked(true); // move isbookmarked to
                 * episode System.out.println("* " + story.getEpisodes().get(i).getSource().toString());
                 * } } } }
                 */
            /*
             * for (int i = 0; i < story.getEpisodes().size(); i ++) { Episode episode =
             * story.getEpisodes().get(i); ArrayList<TraceObject> bookmarkObjects = new
             * ArrayList<TraceObject>(); for (int j = 0; j < episode.getTrace().getTrace().size(); j
             * ++) { if (episode.getTrace().getTrace().get(j) instanceof DOMEventTrace) {
             * DOMEventTrace domEventTrace = (DOMEventTrace)episode.getTrace().getTrace().get(j); if
             * (domEventTrace.getEventType().equals("_BOOKMARK_")) {
             * bookmarkObjects.add(domEventTrace); System.out.println("bookmark"); if (i + 1 <
             * story.getEpisodes().size()) { story.getEpisodes().get(i +
             * 1).getSource().setIsBookmarked(true); } } } }
             * episode.getTrace().getTrace().removeAll(bookmarkObjects); } for (Episode e :
             * story.getEpisodes()) { boolean bookmarkNextEpisode = false; // if
             * (e.getSource().getIsBookmarked()) System.out.println("============ " +
             * e.getSource().getIsBookmarked()); for (TraceObject to : e.getTrace().getTrace()) { if
             * (to instanceof DOMEventTrace) { if
             * (((DOMEventTrace)to).getEventType().equals("_BOOKMARK_"))
             * System.out.println("bookmark"); } } }
             */
            /*
             * for (Episode episode : story.getEpisodes()) { if (episode.getSource() instanceof
             * DOMEventTrace) { if
             * (((DOMEventTrace)episode.getSource()).getTargetElement().contains("bookmarkButton"))
             * { System.out.print("**** " + ((DOMEventTrace)episode.getSource()).getEventType() +
             * " * "); } System.out.println("---- " +
             * ((DOMEventTrace)episode.getSource()).getTargetElement()); } }
             */// TODO TODO TODO project specific for photo gallery. eliminate unwanted episodes
            /**
            story.removeUselessEpisodes();
                
            ss = story.getEpisodes();
            it2 = ss.iterator();
            System.out.println("hhhmmm2");
                
            while (it2.hasNext()) {
            System.out.println("--------");
            System.out.println(it2.next().getSource().getClass());
            }
                
            ArrayList<Episode> bookmarkEpisodes = new ArrayList<Episode>();
                
            for (int i = 0; i < story.getEpisodes().size(); i++) {
            Episode episode = story.getEpisodes().get(i);
            if (episode.getSource() instanceof DOMEventTrace) {
                DOMEventTrace source = (DOMEventTrace) episode.getSource();
                if (source.getTargetElement().contains("bookmarkButton")) {
                    bookmarkEpisodes.add(episode);
                    if (i + 1 < story.getEpisodes().size()) {
                        story.getEpisodes().get(i + 1).setIsBookmarked(true);
                        // story.getEpisodes().get(i).getSource().setIsBookmarked(true); // move
                        // isbookmarked to episode
                        System.out.println("* episode # " + (i + 1) + " bookmarked");
                    }
                }
            }
                
            }
                
            story.removeUselessEpisodes(bookmarkEpisodes);
                
            //    story.removeToolbarEpisodes();
                
            System.out.println("# of episodes after trimming: " + story.getEpisodes().size());
                
            DateFormat dateFormat = new SimpleDateFormat("EEE,d,MMM,HH-mm");
            Date date = new Date();
            System.out.println(dateFormat.format(date));
            // dateFormat.format(date).toString()
            theTime = new String(dateFormat.format(date).toString());
            System.out.println(theTime);
                
            // JavaScript episodes for JSUML2
            Helper.directoryCheck(outputFolder + "/sequence_diagrams/");
            PrintStream JSepisodes =
                new PrintStream(outputFolder + "/sequence_diagrams/allEpisodes.js");
                
            for (Episode e : story.getEpisodes()) {
            // Create pic files for each episode's sequence diagram
            designSequenceDiagram(e, JSepisodes);
            }
                
            // Once all episodes have been saved to JS file, close
            JSepisodes.close();
            **/
            // Create graph containing all episodes with embedded sequence diagrams
            /**            EpisodeGraph eg = new EpisodeGraph(getOutputFolder(), story.getEpisodes());
                        eg.createGraph();
                        writeStoryToDisk();
            **/
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /////    protected static void createDynamicCallGraph(Collection<TraceObject> functionTraces) {
    /******************
    Stack<Function>
        
    ******************/

    /////    }

    /**
     public static void designSequenceDiagram(Episode e, PrintStream jSepisodes) {
    // Given an episode (source, trace included), a pic file will be created
    // in clematis-output/ftrace/sequence_diagrams
        
    /*
     * SequenceDiagram sd = new SequenceDiagram(getOutputFolder(), e); sd.createComponents();
     * sd.createMessages(); sd.close();
     *
    try {
        JSUml2Story jsu2story = new JSUml2Story(jSepisodes, e);
        jsu2story.createComponents();
        jsu2story.createMessages();
        jsu2story.close();
        
    } catch (FileNotFoundException e1) {
        System.out.println("Error initializing print stream for allEpisodes.js");
        e1.printStackTrace();
    } catch (IOException e2) {
        System.out.println("IOException while printing episodes to JS.");
        e2.printStackTrace();
    }
     }
    **/

    /**
     * This method sorts all four groups of trace objects into one ordered list of trace objects
     */
    /**
    private static ArrayList<TraceObject> sortTraceObjects() {
    ArrayList<TraceObject> sortedTrace = new ArrayList<TraceObject>();
        
    ArrayList<Collection<TraceObject>> allCollections =
            new ArrayList<Collection<TraceObject>>();
        
    if (story.getDomEventTraces().size() > 0) {
        allCollections.add(story.getDomEventTraces());
    }
        
    if (story.getFunctionTraces().size() > 0) {
        allCollections.add(story.getFunctionTraces());
    }
        
    if (story.getTimingTraces().size() > 0) {
        allCollections.add(story.getTimingTraces());
    }
        
    if (story.getXhrTraces().size() > 0) {
        allCollections.add(story.getXhrTraces());
    }
        
    if (allCollections.size() == 0) {
        System.out.println("No log");
        return null;
    }
        
    ArrayList<Integer> currentIndexInCollection = new ArrayList<Integer>();
    for (int i = 0; i < allCollections.size(); i++)
        currentIndexInCollection.add(0);
        
    while (true) {
        int currentMinArray = 0;
        
        for (int i = 0; i < allCollections.size(); i++) {
            TraceObject traceObj =
                    Iterables.get(allCollections.get(i), currentIndexInCollection.get(i));
            TraceObject currObj =
                    Iterables.get(allCollections.get(currentMinArray),
                            currentIndexInCollection.get(currentMinArray));
            if (traceObj.getCounter() < currObj.getCounter())
                currentMinArray = i;
        }
        
        sortedTrace.add(Iterables.get(allCollections.get(currentMinArray),
                currentIndexInCollection.get(currentMinArray)));
        
        currentIndexInCollection.set(currentMinArray,
                currentIndexInCollection.get(currentMinArray) + 1);
        if (currentIndexInCollection.get(currentMinArray) >= allCollections.get(
                currentMinArray).size()) {
            allCollections.remove(currentMinArray);
            currentIndexInCollection.remove(currentMinArray);
            if (allCollections.size() == 0)
                break;
        }
    }
        
    return sortedTrace;
    }
    **/
    /**
    private static ArrayList<Episode> buildEpisodes() {
    ArrayList<Episode> episodes = new ArrayList<Episode>();
    int i, j, previousEpisodeEnd = 0;
        
    if (story == null)
        return episodes;
        
    for (i = 0; i < story.getOrderedTraceList().size(); i++) {
        // Iterate through all TraceObjects and identify episodes
        TraceObject sourceTraceObj = story.getOrderedTraceList().get(i);
        
        if (sourceTraceObj.isEpisodeSource()) {
            // && !(sourceTraceObj.getClass().toString().contains("TimeoutCallback"))
            // && !(sourceTraceObj.getClass().toString().contains("XMLHttpRequestResponse"))) {
            // Simple case
            // If the TraceObject is the beginning of an episode
            // i.e. DOMEvent, XHRRequest, create an episode
            Episode episode = new Episode(sourceTraceObj);
        
            for (j = i + 1; j < story.getOrderedTraceList().size(); j++) {
                // Go through the succeeding TraceObjects looking for the
                // end of the episode (as indicated by another episode starter
                // (DOMEvent, TimingEvent, etc.)
        
                TraceObject currentTraceObj = story.getOrderedTraceList().get(j);
        
                if (Math.abs(currentTraceObj.getTimeStamp() - sourceTraceObj.getTimeStamp()) < 120
                        || Math.abs(story.getOrderedTraceList().get(j-1).getTimeStamp() - currentTraceObj.getTimeStamp()) < 5) {
                    // If the succeeding TraceObject is not the beginning of
                    // another episode, add it to the current episode
                    episode.addToTrace(currentTraceObj);
                } else {
                    // End of current episode, break out of inner-loop
                    break;
                }
            }
            // Add the newly created episode to the list of episodes
            episodes.add(episode);
            // Update i to the end of the newly created episode
            i = j - 1;
            previousEpisodeEnd = i;
        
        } else if (sourceTraceObj.getClass().toString().contains("TimeoutCallback")
                || sourceTraceObj.getClass().toString().contains("XMLHttpRequestResponse")) {
            // Special case
            // TimeoutCallback is triggered after the callback function
            // of a timeout has completed. Therefore, have to search backwards in
            // Episode.
            // e.g. FunctionEnter -> FunctionEnter -> FuntionExit -> FunctionExit ->
            // TimeoutCallback
            // As opposed to DOMEvent:
            // DOMEvent -> FunctionEnter -> FunctionEnter -> FuntionExit -> FunctionExit
        
            Episode episode = new Episode(sourceTraceObj);
        
            for (j = previousEpisodeEnd + 1; j < i; j++) {
                // Iterate from end of last episode to this TimeoutCallback
                TraceObject currentTraceObj = story.getOrderedTraceList().get(j);
                episode.addToTrace(currentTraceObj);
            }
        
            // Add the newly created episode to the list of episodes
            episodes.add(episode);
            previousEpisodeEnd = i;
        
        }
        
    }
    return episodes;
    }
    **/
    /**
     * @return Name of the file.
     */
    public static String getFilename() {
        return traceFilename;
    }

    public static String getOutputFolder() {
        return Helper.addFolderSlashIfNeeded(outputFolder);
    }

    public void setOutputFolder(String absolutePath) {
        outputFolder = absolutePath;
    }

    /**
     * Dirty way to save program points from the proxy request threads. TODO: Frank, find cleaner
     * way.
     * 
     * @param string
     *            The JSON-text to save.
     */
    public static void addPoint(String string) {
        JSONArray buffer = null;
        JSONObject targetAttributes = null;
        JSONObject targetElement = null;
        String JSONLabel = new String();
        int i;

        try {
            /* save the current System.out for later usage */
            PrintStream oldOut = System.out;
            /* redirect it to the file */
            System.setOut(output);

            buffer = new JSONArray(string);
            for (i = 0; i < buffer.length(); i++) {

                if (points.length() > 0) {
                    // Add comma after previous trace object
                    System.out.println(",");
                }

                points.put(buffer.getJSONObject(i));

                if (buffer.getJSONObject(i).has("args")
                        && ((String) buffer.getJSONObject(i).get("messageType")).contains("FUNCTION_ENTER")) {
                    try {
                        JSONArray args = (JSONArray) buffer.getJSONObject(i).get("args");
                        String newValue = args.toString();
                        buffer.getJSONObject(i).remove("args");
                        buffer.getJSONObject(i).put("args", newValue);
                    } catch (JSONException jse) {
                        // argument is not a JSON object
                        continue;
                    }
                }
                if (buffer.getJSONObject(i).has("returnValue")
                        && !buffer.getJSONObject(i).get("returnValue").getClass().toString().contains("Null")) {
                    try {
                        JSONObject rv = (JSONObject) buffer.getJSONObject(i).get("returnValue");
                        String newValue = rv.toString();
                        buffer.getJSONObject(i).remove("returnValue");
                        buffer.getJSONObject(i).put("returnValue", newValue);
                    } catch (JSONException jse) {
                        // argument is not a JSON object
                        continue;
                    }
                }
                if (buffer.getJSONObject(i).has("targetElement")) {
                    JSONArray extractedArray = new JSONArray(
                            buffer.getJSONObject(i).get("targetElement").toString());
                    try {
                        targetAttributes = extractedArray.getJSONObject(1);
                        String targetType = extractedArray.get(0).toString();

                        targetElement = new JSONObject("{\"elementType\":\"" + targetType + "\",\"attributes\":"
                                + targetAttributes.toString() + "}");

                    } catch (Exception e) {
                        // targetElement is not usual DOM element
                        // E.g. DOMContentLoaded
                        if (buffer.getJSONObject(i).has("eventType")
                                && buffer.getJSONObject(i).get("eventType").toString().contains("ContentLoaded")) {
                            targetElement = new JSONObject("{\"elementType\":\"DOCUMENT\",\"attributes\":\"-\"}");
                        } else {
                            targetElement = new JSONObject("{\"elementType\":\"UNKNOWN\",\"attributes\":\"-\"}");
                        }
                    }
                    buffer.getJSONObject(i).remove("targetElement");
                    buffer.getJSONObject(i).put("targetElement", targetElement.toString());
                }

                // Insert @class key for Jackson mapping
                if (buffer.getJSONObject(i).has("messageType")) {
                    String mType = buffer.getJSONObject(i).get("messageType").toString();

                    // Maybe better to change mType to ENUM and use switch
                    // instead of 'if's
                    if (mType.contains("FUNCTION_CALL")) {
                        buffer.getJSONObject(i).put("@class", "com.proteus.core.trace.FunctionCall");
                        JSONLabel = "\"FunctionTrace\":";
                    } else if (mType.contains("FUNCTION_ENTER")) {
                        buffer.getJSONObject(i).put("@class", "com.proteus.core.trace.FunctionEnter");
                        JSONLabel = "\"FunctionTrace\":";
                    } else if (mType.contains("FUNCTION_EXIT")) {
                        buffer.getJSONObject(i).put("@class", "com.proteus.core.trace.FunctionExit");
                        JSONLabel = "\"FunctionTrace\":";
                    } else if (mType.contains("RETURN_STATEMENT")) {
                        buffer.getJSONObject(i).put("@class", "com.proteus.core.trace.FunctionReturnStatement");
                        JSONLabel = "\"FunctionTrace\":";
                    } else if (mType.contains("DOM_EVENT")) {
                        buffer.getJSONObject(i).put("@class", "com.proteus.core.trace.DOMEventTrace");
                        JSONLabel = "\"DOMEventTrace\":";
                    } /* else if (mType.contains("DOM_MUTATION")) {
                      buffer.getJSONObject(i).put("@class",
                              "com.clematis.core.trace.DOMMutationTrace");
                      JSONLabel = "\"DOMEventTrace\":";
                      } else if (mType.contains("DOM_ELEMENT_VALUE")) {
                      buffer.getJSONObject(i).put("@class",
                              "com.clematis.core.trace.DOMElementValueTrace");
                      JSONLabel = "\"DOMEventTrace\":";
                      } else if (mType.contains("TIMEOUT_SET")) {
                      buffer.getJSONObject(i).put("@class",
                              "com.clematis.core.trace.TimeoutSet");
                      JSONLabel = "\"TimingTrace\":";
                      } else if (mType.contains("TIMEOUT_CALLBACK")) {
                      buffer.getJSONObject(i).put("@class",
                              "com.clematis.core.trace.TimeoutCallback");
                      JSONLabel = "\"TimingTrace\":";
                      } else if (mType.contains("XHR_OPEN")) {
                      buffer.getJSONObject(i).put("@class",
                              "com.clematis.core.trace.XMLHttpRequestOpen");
                      JSONLabel = "\"XHRTrace\":";
                      } else if (mType.contains("XHR_SEND")) {
                      buffer.getJSONObject(i).put("@class",
                              "com.clematis.core.trace.XMLHttpRequestSend");
                      JSONLabel = "\"XHRTrace\":";
                      } else if (mType.contains("XHR_RESPONSE")) {
                      buffer.getJSONObject(i).put("@class",
                              "com.clematis.core.trace.XMLHttpRequestResponse");
                      JSONLabel = "\"XHRTrace\":";
                      }
                      */
                    // messageType obsolete
                    buffer.getJSONObject(i).remove("messageType");
                }

                System.out.print(JSONLabel + "[" + buffer.getJSONObject(i).toString(2) + "]");
            }

            /* Restore the old system.out */
            System.setOut(oldOut);

            if (i > 0) {
                counter = buffer.getJSONObject(buffer.length() - 1).getInt("counter") + 1;
            }

        } catch (JSONException e) {
            e.printStackTrace();
        }

    }

    public static int getCounter() {
        return counter;
    }

}