gt: 		        + JS_GRAPH), false);
context: /*
   * CrawlOverview is a plugin for Crawljax that generates a nice HTML report to visually see the
   * inferred state graph. 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.crawljax.plugins.crawloverview;
  
  import java.awt.Dimension;
  import java.awt.Point;
  import java.io.File;
  import java.io.FileWriter;
  import java.io.IOException;
  import java.util.ArrayList;
  import java.util.HashMap;
  import java.util.List;
  import java.util.Map;
  
  import org.apache.log4j.Logger;
  import org.apache.velocity.VelocityContext;
  import org.apache.velocity.app.VelocityEngine;
  import org.openqa.selenium.RenderedWebElement;
  import org.openqa.selenium.WebElement;
  
  import com.crawljax.core.CandidateElement;
  import com.crawljax.core.CrawlSession;
  import com.crawljax.core.CrawljaxException;
  import com.crawljax.core.plugin.GeneratesOutput;
  import com.crawljax.core.plugin.OnNewStatePlugin;
  import com.crawljax.core.plugin.PostCrawlingPlugin;
  import com.crawljax.core.plugin.PreStateCrawlingPlugin;
  import com.crawljax.core.state.Eventable;
  import com.crawljax.core.state.StateFlowGraph;
  import com.crawljax.core.state.StateVertix;
  import com.crawljax.util.Helper;
  
  /**
   * Overviewplugin is a plugin that generates a HTML report from the crawling session which can be
   * used to inspect what is crawled by Crawljax The report contains screenshots of the visited states
   * and the clicked elements are highlighted. The report also contains the state-flow graph in which
   * the visited states are linked together. WARNING: This plugin is still in alpha development!
   * 
   * @author dannyroest@gmail.com (Danny Roest)
   * @version $Id$
   **/
  public class CrawlOverview
          implements OnNewStatePlugin, PreStateCrawlingPlugin, PostCrawlingPlugin, GeneratesOutput {
  
  	private static final Logger LOGGER = Logger.getLogger(CrawlOverview.class);
  
  	private String outputFolder = "";
  
  	private static final String MAIN_OUTPUTFOLDER = "crawloverview/";
  	private static final String SCREENSHOTS_FOLDER = "screenshots/";
  	private static final String STATES_FOLDER = "states/";
  
  	private static final String RESOURCES_FOLDER = "plugins/crawloverview/";
  	private static final String TEMPLATE_STATE = "state.vm";
  	private static final String TEMPLATE_INDEX = "index.vm";
  	private static final String JS_FOLDER = "js/";
  	private static final String JS_GRAPH = "graph.js";
  	private static final String JS_PROTOTYPE = "prototype-1.4.0.js";
  
  	private static final int HEADER_SIZE = 20;
  	private static final String COLOR_NEW_STATE = "green";
  	private static final String COLOR_A_PREVIOUS_STATE = "#00FFFF";
  	private static final String COLOR_NO_STATE_CHANGE = "orange";
  
  	private static final Map<String, List<RenderedCandidateElement>> stateCandidatesMap =
  	        new HashMap<String, List<RenderedCandidateElement>>();
  
  	private CrawlSession session;
  
  	private final List<String> visitedStates = new ArrayList<String>();
  
  	/**
  	 * Logs all the canidate elements so that the plugin knows which elements were the candidate
  	 * elements.
  	 */
  	@Override
  	public void preStateCrawling(CrawlSession session, List<CandidateElement> candidateElements) {
  		this.session = session;
  		for (CandidateElement element : candidateElements) {
  			findElementAndAddToMap(session.getCurrentState(), element);
  		}
  	}
  
  	/**
  	 * Saves a screenshot of every new state.
  	 */
  	@Override
  	public void onNewState(CrawlSession session) {
  		this.session = session;
  		saveScreenshot(session.getCurrentState());
  	}
  
  	/**
  	 * Generated the report.
  	 */
  	@Override
  	public void postCrawling(CrawlSession session) {
  		this.session = session;
  		try {
  			Helper.directoryCheck(getOutputFolder() + MAIN_OUTPUTFOLDER + STATES_FOLDER);
  		} catch (IOException e) {
  			LOGGER.error(e.getMessage(), e);
  		}
  		generateOverviewReport();
  	}
  
  	private void saveScreenshot(StateVertix currentState) {
  		if (!visitedStates.contains(currentState.getName())) {
  			String fileName = getScreenShotFileName(currentState);
  			try {
  				Helper.directoryCheck(getOutputFolder() + MAIN_OUTPUTFOLDER + SCREENSHOTS_FOLDER);
  				Helper.checkFolderForFile(fileName);
  			} catch (IOException e) {
  				LOGGER.error(e.getMessage(), e);
  			}
  
  			try {
  				session.getBrowser().saveScreenShot(new File(fileName));
  			} catch (Exception e) {
  				LOGGER.warn("Screenshots are not supported for " + session.getBrowser());
  			}
  
  			visitedStates.add(currentState.getName());
  		}
  	}
  
  	private void generateOverviewReport() {
  		StateFlowGraph sfg = session.getStateFlowGraph();
  		try {
  			createNeededJavaScriptFiles();
  			writeIndexFile();
  		} catch (IOException e) {
  			LOGGER.error(e.getMessage(), e);
  		} catch (Exception e) {
  			LOGGER.error(e.getMessage(), e);
  		}
  
  		for (StateVertix state : sfg.getAllStates()) {
  			List<RenderedCandidateElement> rendered = stateCandidatesMap.get(state.getName());
  			try {
  				writeHtmlForState(state, rendered);
  			} catch (Exception e) {
  				LOGGER.error(e.getMessage(), e);
  			}
  		}
  		File indexFile = new File(getIndexFileName());
  		LOGGER.info("Overview report generated: " + indexFile.getAbsolutePath());
  	}
  
  	private Eventable getEventableByCandidateElementInState(StateVertix state,
  	        RenderedCandidateElement element) {
  		StateFlowGraph sfg = session.getStateFlowGraph();
  		for (Eventable eventable : sfg.getOutgoingClickables(state)) {
  			// TODO Check if element.getIdentification().getValue() is correct replacement for
  			// element.getXpath()
  			if (eventable.getIdentification().getValue().equals(
  			        element.getIdentification().getValue())) {
  				return eventable;
  			}
  		}
  		return null;
  	}
  
  	private int getStateNumber(StateVertix state) {
  		if (state.getName().equals("index")) {
  			return 0;
  		}
  		return Integer.parseInt(state.getName().replace("state", ""));
  	}
  
  	private List<Map<String, String>> getElements(StateFlowGraph sfg, StateVertix state,
  	        List<RenderedCandidateElement> rendered) {
  		List<Map<String, String>> elements = new ArrayList<Map<String, String>>();
  
  		if (rendered != null) {
  			for (RenderedCandidateElement element : rendered) {
  				Eventable eventable = getEventableByCandidateElementInState(state, element);
  				StateVertix toState = null;
  				Map<String, String> elementMap = new HashMap<String, String>();
  				if (eventable != null) {
  					toState = sfg.getTargetState(eventable);
  				}
  				elementMap.put("left", "" + (element.getLocation().x - 1));
  				elementMap.put("top", "" + (element.getLocation().y + 20 - 1));
  				elementMap.put("width", "" + (element.getSize().width + 2));
  				elementMap.put("height", "" + (element.getSize().height + 2));
  				if (toState != null) {
  					elementMap.put("targetname", toState.getName());
  				} else {
  					elementMap.put("targetname", "");
  				}
  				if (toState != null) {
  					if (getStateNumber(toState) < getStateNumber(state)) {
  						// state already found
  						elementMap.put("color", COLOR_A_PREVIOUS_STATE);
  					} else {
  						// new state
  						elementMap.put("color", COLOR_NEW_STATE);
  					}
  					elementMap.put("zindex", "20");
  					elementMap.put("cursor", "pointer");
  				} else {
  					// no state change ater clicking or is not clicked
  					elementMap.put("color", COLOR_NO_STATE_CHANGE);
  					elementMap.put("cursor", "normal");
  					elementMap.put("zindex", "10");
  				}
  				elements.add(elementMap);
  			}
  		}
  
  		return elements;
  	}
  
  	private List<Map<String, String>> getEventables(StateFlowGraph sfg) throws CrawljaxException {
  		List<Map<String, String>> eventables = new ArrayList<Map<String, String>>();
  		for (Eventable eventable : sfg.getAllEdges()) {
  			Map<String, String> eventableMap = new HashMap<String, String>();
  			eventableMap.put("from", eventable.getSourceStateVertix().getName());
  			eventableMap.put("to", eventable.getTargetStateVertix().getName());
  			eventables.add(eventableMap);
  		}
  		return eventables;
  	}
  
  	private List<Map<String, String>> getStates(StateFlowGraph sfg) {
  		List<Map<String, String>> states = new ArrayList<Map<String, String>>();
  		for (StateVertix stateVertix : sfg.getAllStates()) {
  			Map<String, String> stateVertixMap = new HashMap<String, String>();
  			stateVertixMap.put("name", stateVertix.getName());
  			stateVertixMap.put("url", stateVertix.getUrl());
  			stateVertixMap.put("id", stateVertix.getName().replace("state", "S"));
  			stateVertixMap.put("screenshot", SCREENSHOTS_FOLDER + stateVertix.getName() + ".png");
  			states.add(stateVertixMap);
  		}
  		return states;
  	}
  
  	private void createNeededJavaScriptFiles() throws IOException {
  		Helper.directoryCheck(this.getOutputFolder() + MAIN_OUTPUTFOLDER + JS_FOLDER);
  		File jsGraph =
  		        new File(this.getOutputFolder() + MAIN_OUTPUTFOLDER + JS_FOLDER + JS_GRAPH);
  		Helper.writeToFile(jsGraph.getAbsolutePath(), Helper.getTemplateAsString(RESOURCES_FOLDER
  