@Override
  protected List<SeriesValue> loadSeries(AbstractBuild build) throws IOException {
    List<SeriesValue> values = new ArrayList<SeriesValue>();

    FilePath seriesFile = new FilePath(build.getWorkspace(), getFile());

    try {
      if (!seriesFile.exists()) {
        return values;
      }

      XPathExpression expression = XPathFactory.newInstance().newXPath().compile(xpath);

      InputSource source = null;

      try {
        source = new InputSource(seriesFile.read());

        Object xml = expression.evaluate(source, getNodeQName());

        if (NODESET.equals(getNodeQName())) {
          NodeList list = (NodeList) xml;

          for (int nodeIndex = 0; nodeIndex < list.getLength(); nodeIndex++) {
            Node node = list.item(nodeIndex);

            String localLabel = node.getLocalName(), value = node.getTextContent();

            if (localLabel != null && value != null) {
              values.add(createPlotPoint(value.trim(), localLabel, build));
            }
          }
        } else {
          values.add(createPlotPoint(nodeToString(xml), label, build));
        }
      } finally {
        if (source != null) {
          source.getByteStream().close();
        }
      }

      return values;
    } catch (XPathExpressionException e) {
      throw new RuntimeException(e);
    } catch (InterruptedException e) {
      throw new RuntimeException(e);
    }
  }
    public FilePath[] invoke(File f, VirtualChannel channel)
        throws IOException, InterruptedException {
      FilePath[] r = new FilePath(f).list(reportFilePath);

      XMLInputFactory factory = XMLInputFactory.newInstance();
      factory.setProperty("javax.xml.stream.supportDTD", false);

      for (FilePath filePath : r) {
        InputStream is = null;
        XMLEventReader reader = null;
        try {
          is = filePath.read();
          reader = factory.createXMLEventReader(is);
          while (reader.hasNext()) {
            XMLEvent event = reader.nextEvent();
            if (event.isStartElement()) {
              StartElement start = (StartElement) event;
              if (start.getName().getLocalPart().equals("coverage")) {
                // This is a cobertura coverage report file
                break;
              } else {
                throw new IOException(
                    filePath
                        + " is not a cobertura coverage report, please check your report pattern");
              }
            }
          }
        } catch (XMLStreamException e) {
          throw new IOException(filePath + " is not an XML file, please check your report pattern");
        } finally {
          try {
            if (reader != null) {
              try {
                reader.close();
              } catch (XMLStreamException ex) {
                //
              }
            }
          } finally {
            IOUtils.closeQuietly(is);
          }
        }
      }
      return r;
    }
  /**
   * Constructs the object from emma XML report files. See <a
   * href="http://emma.sourceforge.net/coverage_sample_c/coverage.xml">an example XML file</a>.
   *
   * @throws IOException if failed to parse the file.
   */
  public static EmmaBuildAction load(
      AbstractBuild<?, ?> owner,
      Rule rule,
      EmmaHealthReportThresholds thresholds,
      FilePath... files)
      throws IOException {
    Ratio ratios[] = null;
    for (FilePath f : files) {
      InputStream in = f.read();
      try {
        ratios = loadRatios(in, ratios);
      } catch (XmlPullParserException e) {
        throw new IOException2("Failed to parse " + f, e);
      } finally {
        in.close();
      }
    }

    return new EmmaBuildAction(
        owner, rule, ratios[0], ratios[1], ratios[2], ratios[3], ratios[4], thresholds);
  }
 @Override
 public InputStream streamFileInWorkspace(String relLocation) throws IOException {
   FilePath filePath = locateValidFileInWorkspace(relLocation);
   return filePath.read();
 }
  /**
   * Serves a file from the file system (Maps the URL to a directory in a file system.)
   *
   * @param icon The icon file name, like "folder-open.gif"
   * @param serveDirIndex True to generate the directory index. False to serve "index.html"
   * @deprecated as of 1.297 Instead of calling this method explicitly, just return the {@link
   *     DirectoryBrowserSupport} object from the {@code doXYZ} method and let Stapler generate a
   *     response for you.
   */
  public void serveFile(
      StaplerRequest req, StaplerResponse rsp, FilePath root, String icon, boolean serveDirIndex)
      throws IOException, ServletException, InterruptedException {
    // handle form submission
    String pattern = req.getParameter("pattern");
    if (pattern == null) pattern = req.getParameter("path"); // compatibility with Hudson<1.129
    if (pattern != null) {
      rsp.sendRedirect2(pattern);
      return;
    }

    String path = getPath(req);
    if (path.replace('\\', '/').indexOf("/../") != -1) {
      // don't serve anything other than files in the artifacts dir
      rsp.sendError(HttpServletResponse.SC_BAD_REQUEST);
      return;
    }

    // split the path to the base directory portion "abc/def/ghi" which doesn't include any
    // wildcard,
    // and the GLOB portion "**/*.xml" (the rest)
    StringBuilder _base = new StringBuilder();
    StringBuilder _rest = new StringBuilder();
    int restSize = -1; // number of ".." needed to go back to the 'base' level.
    boolean zip = false; // if we are asked to serve a zip file bundle
    boolean plain = false; // if asked to serve a plain text directory listing
    {
      boolean inBase = true;
      StringTokenizer pathTokens = new StringTokenizer(path, "/");
      while (pathTokens.hasMoreTokens()) {
        String pathElement = pathTokens.nextToken();
        // Treat * and ? as wildcard unless they match a literal filename
        if ((pathElement.contains("?") || pathElement.contains("*"))
            && inBase
            && !(new FilePath(root, (_base.length() > 0 ? _base + "/" : "") + pathElement)
                .exists())) inBase = false;
        if (pathElement.equals("*zip*")) {
          // the expected syntax is foo/bar/*zip*/bar.zip
          // the last 'bar.zip' portion is to causes browses to set a good default file name.
          // so the 'rest' portion ends here.
          zip = true;
          break;
        }
        if (pathElement.equals("*plain*")) {
          plain = true;
          break;
        }

        StringBuilder sb = inBase ? _base : _rest;
        if (sb.length() > 0) sb.append('/');
        sb.append(pathElement);
        if (!inBase) restSize++;
      }
    }
    restSize = Math.max(restSize, 0);
    String base = _base.toString();
    String rest = _rest.toString();

    // this is the base file/directory
    FilePath baseFile = new FilePath(root, base);

    if (baseFile.isDirectory()) {
      if (zip) {
        rsp.setContentType("application/zip");
        baseFile.zip(rsp.getOutputStream(), rest);
        return;
      }
      if (plain) {
        rsp.setContentType("text/plain;charset=UTF-8");
        OutputStream os = rsp.getOutputStream();
        try {
          for (String kid : baseFile.act(new SimpleChildList())) {
            os.write(kid.getBytes("UTF-8"));
            os.write('\n');
          }
          os.flush();
        } finally {
          os.close();
        }
        return;
      }

      if (rest.length() == 0) {
        // if the target page to be displayed is a directory and the path doesn't end with '/',
        // redirect
        StringBuffer reqUrl = req.getRequestURL();
        if (reqUrl.charAt(reqUrl.length() - 1) != '/') {
          rsp.sendRedirect2(reqUrl.append('/').toString());
          return;
        }
      }

      FileCallable<List<List<Path>>> glob = null;

      if (rest.length() > 0) {
        // the rest is Ant glob pattern
        glob = new PatternScanner(rest, createBackRef(restSize));
      } else if (serveDirIndex) {
        // serve directory index
        glob = new ChildPathBuilder();
      }

      if (glob != null) {
        // serve glob
        req.setAttribute("it", this);
        List<Path> parentPaths = buildParentPath(base, restSize);
        req.setAttribute("parentPath", parentPaths);
        req.setAttribute("backPath", createBackRef(restSize));
        req.setAttribute("topPath", createBackRef(parentPaths.size() + restSize));
        req.setAttribute("files", baseFile.act(glob));
        req.setAttribute("icon", icon);
        req.setAttribute("path", path);
        req.setAttribute("pattern", rest);
        req.setAttribute("dir", baseFile);
        req.getView(this, "dir.jelly").forward(req, rsp);
        return;
      }

      // convert a directory service request to a single file service request by serving
      // 'index.html'
      baseFile = baseFile.child(indexFileName);
    }

    // serve a single file
    if (!baseFile.exists()) {
      rsp.sendError(HttpServletResponse.SC_NOT_FOUND);
      return;
    }

    boolean view = rest.equals("*view*");

    if (rest.equals("*fingerprint*")) {
      rsp.forward(Hudson.getInstance().getFingerprint(baseFile.digest()), "/", req);
      return;
    }

    ContentInfo ci = baseFile.act(new ContentInfo());

    if (LOGGER.isLoggable(Level.FINE))
      LOGGER.fine(
          "Serving "
              + baseFile
              + " with lastModified="
              + ci.lastModified
              + ", contentLength="
              + ci.contentLength);

    InputStream in = baseFile.read();
    if (view) {
      // for binary files, provide the file name for download
      rsp.setHeader("Content-Disposition", "inline; filename=" + baseFile.getName());

      // pseudo file name to let the Stapler set text/plain
      rsp.serveFile(req, in, ci.lastModified, -1, ci.contentLength, "plain.txt");
    } else {
      rsp.serveFile(req, in, ci.lastModified, -1, ci.contentLength, baseFile.getName());
    }
  }