/**
  * Write the data back to the file that is represented by the Resource instance
  *
  * @param resource the resource instance to which teh data needs to be written
  * @param actualData the actual data that needs to be written
  * @param methodNames OPTIONAL names of methods for which the data needs to be written. If the
  *     method names are not provided, then the data is written for all the test methods ofr which
  *     teh data is present in the actualData parameter
  */
 public void writeData(
     Resource resource, Map<String, List<Map<String, Object>>> actualData, String... methodNames) {
   try {
     if (methodNames == null || methodNames.length == 0) {
       writeFullDataToSpreadsheet(resource.getOutputStream(), actualData);
     } else {
       for (String methodName : methodNames) {
         writeDataToSpreadsheet(resource, methodName, actualData);
       }
     }
   } catch (IOException e) {
     LOG.warn(
         "Unable to write data to file {} . An I/O Exception occured.",
         resource.getResourceName(),
         e);
   }
 }
  /**
   * Main entry point for the Loader
   *
   * @param resource The resource to load the data from
   * @return a Map representing the loaded data
   */
  public Map<String, List<Map<String, Object>>> loadData(Resource resource) {
    LOG.debug("Trying to load the data for resource :" + resource.getResourceName());
    Map<String, List<Map<String, Object>>> result = null;
    try {
      result = loadFromSpreadsheet(resource.getInputStream());

    } catch (IOException e) {
      LOG.error(
          "IOException occured while trying to Load the resource {} . Moving to the next resource.",
          resource.getResourceName(),
          e);
    }
    if (result != null) {
      LOG.debug(
          "Loading data from resource {} succedded and the data loaded is {}",
          resource.getResourceName(),
          result);
    }

    return result;
  }
  /**
   * Write the data for the given method to the excel sheet
   *
   * @param resource
   * @param methodNameForDataLoad
   * @param data
   * @throws IOException
   */
  private void writeDataToSpreadsheet(
      Resource resource, String methodNameForDataLoad, Map<String, List<Map<String, Object>>> data)
      throws IOException {

    LOG.debug("writeDataToSpreadsheet started" + resource.toString() + data);

    Workbook workbook;
    try {

      workbook = WorkbookFactory.create(new POIFSFileSystem(resource.getInputStream()));

    } catch (Exception e) {
      LOG.error("Error creating WorkbookFactory for resource " + resource.toString(), e);
      throw new IOException();
    }

    Sheet sheet = workbook.getSheetAt(0);

    Integer recordNum = getMethodRowNumFromExcel(sheet, methodNameForDataLoad);
    // if record doesn't exist then return without writing any thing
    if (recordNum == null) {
      LOG.error("Method doesn't exist in the excel:" + methodNameForDataLoad);
      return;
    }
    int columnNum = sheet.getRow(recordNum).getLastCellNum();
    int rowNum = 0;
    boolean isActualResultHeaderWritten = false;
    boolean isTestDurationHeaderWritten = false;
    boolean isHeaderRowNumIncremented = false;

    for (Map<String, Object> methodData : data.get(methodNameForDataLoad)) {
      // rowNum increment by one to proceed with next record of the method.
      rowNum++;

      Object testDuration = methodData.get(DURATION);
      if (testDuration != null) {

        if (!isTestDurationHeaderWritten && recordNum != null) {
          // Write the test duration header.
          writeDataToCell(sheet, recordNum, columnNum, DURATION);
          // increment the rowNum
          rowNum = rowNum + recordNum;
          isTestDurationHeaderWritten = true;
        }

        // Write the actual result and test status values.
        if (isTestDurationHeaderWritten) {
          LOG.debug("testDuration:" + testDuration.toString());
          writeDataToCell(sheet, rowNum, columnNum, testDuration.toString());
        }
      }
      if (!isHeaderRowNumIncremented
          && (isTestDurationHeaderWritten || isActualResultHeaderWritten)) {

        isHeaderRowNumIncremented = true;
      }

      Object actualResult = methodData.get(ACTUAL_RESULT);
      Object testStatus = methodData.get(TEST_STATUS);
      if (actualResult != null) {

        if (!isActualResultHeaderWritten && recordNum != null) {
          // Write the actual result and test status headers.
          writeDataToCell(sheet, recordNum, columnNum + 1, ACTUAL_RESULT);
          if (testStatus != null) writeDataToCell(sheet, recordNum, columnNum + 2, TEST_STATUS);
          // rowNum = rowNum + recordNum;
          isActualResultHeaderWritten = true;
        }
        LOG.debug("rowNum:" + rowNum);

        // Write the actual result and test status values.
        if (isActualResultHeaderWritten) {
          LOG.debug("actualResult:" + actualResult.toString());
          // trim actual result to 30KB if it is more than that
          actualResult = trimActualResult(actualResult.toString());
          writeDataToCell(sheet, rowNum, columnNum + 1, actualResult.toString());

          if (testStatus != null) {
            // Check against trimmed actual result
            Object expectedResult = methodData.get(EXPECTED_RESULT);
            testStatus =
                expectedResult.toString().equals(actualResult.toString())
                    ? Loader.TEST_PASSED
                    : Loader.TEST_FAILED;
            LOG.debug("testStatus:" + testStatus.toString());
            writeDataToCell(sheet, rowNum, columnNum + 2, testStatus.toString());
          }
        }
      }
    }

    // Write the output to a file
    workbook.write(resource.getOutputStream());
    LOG.debug("writeDataToSpreadsheet finished");
  }
 /**
  * Load the Data for the given class or method. This method will try to find {@link DataLoader} on
  * either the class level or the method level. In case the annotation is found, this method will
  * load the data using the specified loader class and then save it in the DataContext for further
  * use by the system. We also create another copy of the input test data that we store in the
  * {@link DataDrivenTestRunner#writableData} field. This is done in order to facilitate the
  * writing of the data that might be returned by the test method.
  *
  * @param testClass the class object, if any.
  * @param method current executing method, if any.
  * @param currentTestClass the currently executing test class. this is used to append in front of
  *     the method name to get unique method names as there could be methods in different classes
  *     with the same name and thus we want to avoid conflicts.
  * @param writableData The writable data that is used internally for reporting purposes
  */
 public static void loadData(
     Class<?> testClass,
     FrameworkMethod method,
     TestClass currentTestClass,
     Map<String, List<Map<String, Object>>> writableData) {
   if (testClass == null && method == null) {
     Assert.fail(
         "The framework should provide either the testClass parameter or the method parameter in order to load the test data.");
   }
   // We give priority to Class Loading and then to method loading
   DataLoader testData = null;
   if (testClass != null) {
     testData = testClass.getAnnotation(DataLoader.class);
   } else {
     testData = method.getAnnotation(DataLoader.class);
   }
   if (testData != null) {
     TestInfo testInfo = DataLoaderUtil.determineLoader(testData, currentTestClass);
     Loader dataLoader = testInfo.getDataLoader();
     if (testInfo.getDataLoader() == null) {
       Assert.fail(
           "The framework currently does not support the specified Loader type. "
               + "You can provide the custom Loader by choosing LoaderType.CUSTOM in TestData "
               + "annotation and providing your custom loader using DataLoader annotation.");
     } else {
       if (testInfo.getFilePaths() == null || testInfo.getFilePaths().length == 0) {
         // implies that there exists a CUSTOM loader that loads the data using Java classes
         Map<String, List<Map<String, Object>>> data = dataLoader.loadData(new EmptyResource());
         // We also maintain the copy of the actual data for our write functionality.
         writableData.putAll(data);
         DataContext.setData(DataConverter.appendClassName(data, currentTestClass.getJavaClass()));
         DataContext.setConvertedData(
             DataConverter.convert(data, currentTestClass.getJavaClass()));
       } else {
         ResourceLoader resourceLoader =
             new ResourceLoaderStrategy(currentTestClass.getJavaClass());
         for (String filePath : testInfo.getFilePaths()) {
           Resource resource = resourceLoader.getResource(filePath);
           try {
             if (resource.exists()) {
               Map<String, List<Map<String, Object>>> data = dataLoader.loadData(resource);
               // We also maintain the copy of the actual data for our write functionality.
               writableData.putAll(data);
               DataContext.setData(
                   DataConverter.appendClassName(data, currentTestClass.getJavaClass()));
               DataContext.setConvertedData(
                   DataConverter.convert(data, currentTestClass.getJavaClass()));
             } else {
               LOG.warn(
                   "Resource {} does not exists in the specified path. If it is a classpath resource, use 'classpath:' "
                       + "before the path name, else check the path.",
                   resource);
             }
           } catch (Exception e) {
             LOG.error(
                 "Exception occured while trying to load the data for resource {}", resource, e);
             throw new RuntimeException(e);
           }
         }
       }
     }
   }
 }