/* * Copyright (c) 2008-2009 Yahoo! Inc. All rights reserved. * The copyrights to the contents of this file are licensed under the MIT License (http://www.opensource.org/licenses/mit-license.php) */ package hudson.plugins.plot; import hudson.FilePath; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.PrintStream; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.logging.Level; import java.util.logging.Logger; import java.util.regex.Matcher; import java.util.regex.Pattern; import javax.servlet.ServletException; import au.com.bytecode.opencsv.CSVReader; import org.kohsuke.stapler.DataBoundConstructor; /** * Represents a plot data series configuration from an CSV file. * * @author Allen Reese * */ public class CSVSeries extends Series { private static transient final Logger LOGGER = Logger.getLogger(Series.class.getName()); // Debugging hack, so I don't have to change FINE/INFO... private static transient final Level defaultLogLevel = Level.FINEST; private static transient final Pattern PAT_COMMA = Pattern.compile(","); private static transient final Pattern PAT_NAME = Pattern.compile("%name%"); private static transient final Pattern PAT_INDEX = Pattern.compile("%index%"); public static enum InclusionFlag { OFF, INCLUDE_BY_STRING, EXCLUDE_BY_STRING, INCLUDE_BY_COLUMN, EXCLUDE_BY_COLUMN, }; /** * Set for excluding values by column name */ private Set<String> strExclusionSet; /** * Set for excluding values by column # */ private Set<Integer> colExclusionSet; /** * Flag controlling how values are excluded. */ private InclusionFlag inclusionFlag = InclusionFlag.OFF; /** * Comma separated list of columns to exclude. */ private String exclusionValues; /** * Url to use as a base for mapping points. */ private String url; private boolean displayTableFlag; /** * * @param file * @param label * @param req Stapler request * @param radioButtonId ID used to find the parameters specific to this instance. * @throws ServletException */ @DataBoundConstructor public CSVSeries(String file, String url, String inclusionFlag, String exclusionValues, boolean displayTableFlag) { super(file, "", "csv"); this.url = url; if (exclusionValues == null) { this.inclusionFlag = InclusionFlag.OFF; return; } this.inclusionFlag = InclusionFlag.valueOf(inclusionFlag); this.exclusionValues = exclusionValues; this.displayTableFlag = displayTableFlag; loadExclusionSet(); } public String getInclusionFlag() { return inclusionFlag.toString(); } public String getExclusionValues() { return exclusionValues; } public String getUrl() { return url; } public boolean getDisplayTableFlag() { return displayTableFlag; } /** * Load the series from a properties file. */ @Override public PlotPoint[] loadSeries(FilePath workspaceRootDir, PrintStream logger) { CSVReader reader = null; InputStream in = null; InputStreamReader inputReader = null; try { List<PlotPoint> ret = new ArrayList<PlotPoint>(); FilePath[] seriesFiles = null; try { seriesFiles = workspaceRootDir.list(getFile()); } catch (Exception e) { LOGGER.warning("Exception trying to retrieve series files: " + e); return null; } if (seriesFiles != null && seriesFiles.length < 1) { LOGGER.info("No plot data file found: " + workspaceRootDir.getName() + " " + getFile()); return null; } try { if (LOGGER.isLoggable(defaultLogLevel)) LOGGER.log(defaultLogLevel,"Loading plot series data from: " + getFile()); in = seriesFiles[0].read(); } catch (Exception e) { LOGGER.warning("Exception reading plot series data from: " + seriesFiles[0] + " " + e); return null; } if (LOGGER.isLoggable(defaultLogLevel)) LOGGER.log(defaultLogLevel,"Loaded CSV Plot file: " + getFile()); // load existing plot file inputReader = new InputStreamReader(in); reader = new CSVReader(inputReader); String[] nextLine; // save the header line to use it for the plot labels. String[] headerLine=reader.readNext(); // read each line of the CSV file and add to rawPlotData int lineNum=0; while ((nextLine = reader.readNext()) != null) { // skip empty lines if (nextLine.length==1 && nextLine[0].length()==0) continue; for (int index = 0; index < nextLine.length; index++) { String yvalue; String label = null; if (index > nextLine.length) continue; yvalue = nextLine[index]; if (index < headerLine.length) label = headerLine[index]; if (label == null || label.length()<=0) { // if there isn't a label, use the index as the label label = "" + index; } //LOGGER.finest("Loaded point: " + point); // create a new point with the yvalue from the csv file and url from the URL_index in the properties file. if (!excludePoint(label,index)) { PlotPoint point = new PlotPoint(yvalue, getUrl(label,index), label); if (LOGGER.isLoggable(defaultLogLevel)) LOGGER.log(defaultLogLevel,"CSV Point: [" +index + ":" + lineNum +"]"+ point); ret.add(point); } else { if (LOGGER.isLoggable(defaultLogLevel)) LOGGER.log(defaultLogLevel,"excluded CSV Column: " + index + " : " + label); } } ++lineNum; } return ret.toArray(new PlotPoint[ret.size()]); } catch (IOException ioe) { //ignore if (LOGGER.isLoggable(defaultLogLevel)) LOGGER.log(defaultLogLevel,"Exception: " + ioe); } finally { if (in != null) { try { in.close(); } catch (IOException ignore) { //ignore } } if (inputReader != null) { try { inputReader.close(); } catch (IOException ignore) { //ignore } } if (reader != null) { try { reader.close(); } catch (IOException ignore) { //ignore } } } return null; } /** * This function checks the exclusion/inclusion filters from the properties file and * returns true if a point should be excluded. * @return true if the point should be excluded based on label or column */ private boolean excludePoint(String label,int index) { if (inclusionFlag == null || inclusionFlag == InclusionFlag.OFF) return false; boolean retVal=false; switch (inclusionFlag) { case INCLUDE_BY_STRING: // if the set contains it, don't exclude it. retVal = !(strExclusionSet.contains(label)); break; case EXCLUDE_BY_STRING: // if the set doesn't contain it, exclude it. retVal = strExclusionSet.contains(label); break; case INCLUDE_BY_COLUMN: // if the set contains it, don't exclude it. retVal = !(colExclusionSet.contains(Integer.valueOf(index))); break; case EXCLUDE_BY_COLUMN: // if the set doesn't contain it, don't exclude it. retVal = colExclusionSet.contains(Integer.valueOf(index)); break; } if (LOGGER.isLoggable(Level.FINEST)) LOGGER.finest(((retVal)?"excluded":"included") + " CSV Column: " + index + " : " + label); return retVal; } /** * This function loads the set of columns that should be included or excluded. */ private void loadExclusionSet() { if (inclusionFlag == InclusionFlag.OFF) return; if (exclusionValues == null) { inclusionFlag = InclusionFlag.OFF; return; } switch (inclusionFlag) { case INCLUDE_BY_STRING: case EXCLUDE_BY_STRING: strExclusionSet = new HashSet<String>(); break; case INCLUDE_BY_COLUMN: case EXCLUDE_BY_COLUMN: colExclusionSet = new HashSet<Integer>(); break; } for (String str : PAT_COMMA.split(exclusionValues)) { if (str==null || str.length()<=0) continue; switch (inclusionFlag) { case INCLUDE_BY_STRING: case EXCLUDE_BY_STRING: if (LOGGER.isLoggable(Level.FINEST)) LOGGER.finest(inclusionFlag + " CSV Column: " + str); strExclusionSet.add(str); break; case INCLUDE_BY_COLUMN: case EXCLUDE_BY_COLUMN: try { if (LOGGER.isLoggable(Level.FINEST)) LOGGER.finest(inclusionFlag + " CSV Column: " + str); colExclusionSet.add(Integer.valueOf(str)); } catch (NumberFormatException nfe) { // ignore badly formatted columns. } break; } } } /** * Return the url that should be used for this point. * @param label Name of the column * @param index Index of the column * @return url for the label. */ private String getUrl(String label,int index) { /* * Check the name first, and do replacement upon it. */ Matcher nameMatcher = PAT_NAME.matcher(label); if (nameMatcher.find()) { url = nameMatcher.replaceAll(label); } /* * Check the index, and do replacement on it. */ Matcher indexMatcher = PAT_INDEX.matcher(label); if (indexMatcher.find()) { url = indexMatcher.replaceAll(label); } return url; } }