public synchronized boolean processRow( StepMetaInterface smi, StepDataInterface sdi ) throws KettleStepException {
    meta = (ShapeFileReaderMeta) smi;
    data = (ShapeFileReaderData) sdi;
    int partnr;

    boolean retval = true;

    if ( data.shapeNr >= data.shapeFile.getNrShapes() ) {
      setOutputDone();
      return false;
    }

    if ( first ) {
      first = false;

      data.outputRowMeta = new RowMeta();
      meta.getFields( data.outputRowMeta, getStepname(), null, null, this );
    }

    // building new row
    Object[] outputRow = RowDataUtil.allocateRowData( data.outputRowMeta.size() );
    int outputIndex;

    // getting shape # data.shapeNr from shapefile

    // Get the shape from the shapefile
    //
    ShapeInterface si = data.shapeFile.getShape( data.shapeNr );
    switch ( si.getType() ) {
      case Shape.SHAPE_TYPE_POLYLINE_M:
        // PolyLimeM";
        ShapePolyLineM eplm = (ShapePolyLineM) si;
        partnr = 0;
        for ( int j = 0; j < eplm.nrpoints; j++ ) {
          // PolyLimeM, point #"+j;
          for ( int k = 0; k < eplm.nrparts; k++ ) {
            if ( j == eplm.part_starts[ k ] ) {
              partnr++;
            }
          }

          outputIndex = 0;

          // adding the basics";
          // Add the basics...
          // The filename...
          outputRow[ outputIndex++ ] = meta.getShapeFilename();

          // The file type
          outputRow[ outputIndex++ ] = data.shapeFile.getFileHeader().getShapeTypeDesc();

          // The shape nr
          outputRow[ outputIndex++ ] = new Long( data.shapeNr + 1 );

          // The part nr
          outputRow[ outputIndex++ ] = new Long( partnr );

          // The nr of parts
          outputRow[ outputIndex++ ] = new Long( eplm.nrparts );

          // The point nr
          outputRow[ outputIndex++ ] = new Long( j + 1 );

          // The nr of points
          outputRow[ outputIndex++ ] = new Long( eplm.nrpoints );

          // The X coordinate
          outputRow[ outputIndex++ ] = new Double( eplm.point[ j ].x );

          // The Y coordinate
          outputRow[ outputIndex++ ] = new Double( eplm.point[ j ].y );

          // The measure
          outputRow[ outputIndex++ ] = new Double( eplm.measures[ j ] );

          // The Values in the DBF file...
          // PolyLimeM, point #"+j+", add dbf data";
          Object[] dbfData = si.getDbfData();
          RowMetaInterface dbfMeta = si.getDbfMeta();
          for ( int d = 0; d < dbfMeta.size(); d++ ) {
            outputRow[ outputIndex++ ] = dbfData[ d ];
          }

          linesInput++;

          // Put it out to the rest of the world...
          try {
            putRow( data.outputRowMeta, data.outputRowMeta.cloneRow( outputRow ) );
          } catch ( KettleValueException e ) {
            throw new KettleStepException( "Unable to clone row", e );
          }
        }
        break;
      case Shape.SHAPE_TYPE_POLYGON:
        // ShapePolygon";
        ShapePolygon eplg = (ShapePolygon) si;
        partnr = 0;
        for ( int j = 0; j < eplg.nrpoints; j++ ) {
          // PolyLime, point #"+j;
          for ( int k = 0; k < eplg.nrparts; k++ ) {
            if ( j == eplg.part_starts[ k ] ) {
              partnr++;
            }
          }

          outputIndex = 0;

          // adding the basics";
          // Add the basics...
          // The filename...
          outputRow[ outputIndex++ ] = meta.getShapeFilename();

          // The file type
          outputRow[ outputIndex++ ] = data.shapeFile.getFileHeader().getShapeTypeDesc();

          // The shape nr
          outputRow[ outputIndex++ ] = new Long( data.shapeNr + 1 );

          // The part nr
          outputRow[ outputIndex++ ] = new Long( partnr );

          // The nr of parts
          outputRow[ outputIndex++ ] = new Long( eplg.nrparts );

          // The point nr
          outputRow[ outputIndex++ ] = new Long( j + 1 );

          // The nr of points
          outputRow[ outputIndex++ ] = new Long( eplg.nrpoints );

          // The X coordinate
          outputRow[ outputIndex++ ] = new Double( eplg.point[ j ].x );

          // The Y coordinate
          outputRow[ outputIndex++ ] = new Double( eplg.point[ j ].y );

          // The measure
          outputRow[ outputIndex++ ] = new Double( 0.0 );

          // The Values in the DBF file...
          // PolyLime, point #"+j+", add dbf data";
          //
          Object[] dbfData = si.getDbfData();
          RowMetaInterface dbfMeta = si.getDbfMeta();
          for ( int d = 0; d < dbfMeta.size(); d++ ) {
            outputRow[ outputIndex++ ] = dbfData[ d ];
          }
          linesInput++;

          // Put it out to the rest of the world...
          try {
            putRow( data.outputRowMeta, data.outputRowMeta.cloneRow( outputRow ) );
          } catch ( KettleValueException e ) {
            throw new KettleStepException( "Unable to clone row", e );
          }
        }
        break;

      case Shape.SHAPE_TYPE_POLYLINE:
        // PolyLime";
        ShapePolyLine epl = (ShapePolyLine) si;
        partnr = 0;
        for ( int j = 0; j < epl.nrpoints; j++ ) {
          // PolyLime, point #"+j;
          for ( int k = 0; k < epl.nrparts; k++ ) {
            if ( j == epl.part_starts[ k ] ) {
              partnr++;
            }
          }

          outputIndex = 0;

          // adding the basics";
          // Add the basics...
          // The filename...
          outputRow[ outputIndex++ ] = meta.getShapeFilename();

          // The file type
          outputRow[ outputIndex++ ] = data.shapeFile.getFileHeader().getShapeTypeDesc();

          // The shape nr
          outputRow[ outputIndex++ ] = new Long( data.shapeNr + 1 );

          // The part nr
          outputRow[ outputIndex++ ] = new Long( partnr );

          // The nr of parts
          outputRow[ outputIndex++ ] = new Long( epl.nrparts );

          // The point nr
          outputRow[ outputIndex++ ] = new Long( j + 1 );

          // The nr of points
          outputRow[ outputIndex++ ] = new Long( epl.nrpoints );

          // The X coordinate
          outputRow[ outputIndex++ ] = new Double( epl.point[ j ].x );

          // The Y coordinate
          outputRow[ outputIndex++ ] = new Double( epl.point[ j ].y );

          // The measure
          outputRow[ outputIndex++ ] = new Double( 0.0 );

          // The Values in the DBF file...
          // PolyLime, point #"+j+", add dbf data";
          //
          Object[] dbfData = si.getDbfData();
          RowMetaInterface dbfMeta = si.getDbfMeta();
          for ( int d = 0; d < dbfMeta.size(); d++ ) {
            outputRow[ outputIndex++ ] = dbfData[ d ];
          }
          linesInput++;

          // Put it out to the rest of the world...
          try {
            putRow( data.outputRowMeta, data.outputRowMeta.cloneRow( outputRow ) );
          } catch ( KettleValueException e ) {
            throw new KettleStepException( "Unable to clone row", e );
          }
        }
        break;
      case Shape.SHAPE_TYPE_POINT:
        // Point";
        ShapePoint ep = (ShapePoint) si;

        // Add the basics...

        outputIndex = 0;

        // The filename...
        outputRow[ outputIndex++ ] = meta.getShapeFilename();

        // The file type
        outputRow[ outputIndex++ ] = data.shapeFile.getFileHeader().getShapeTypeDesc();

        // The shape nr
        outputRow[ outputIndex++ ] = new Long( data.shapeNr );

        // The part nr
        outputRow[ outputIndex++ ] = new Long( 0L );

        // The nr of parts
        outputRow[ outputIndex++ ] = new Long( 0L );

        // The point nr
        outputRow[ outputIndex++ ] = new Long( 0L );

        // The nr of points
        outputRow[ outputIndex++ ] = new Long( 0L );

        // The X coordinate
        outputRow[ outputIndex++ ] = new Double( ep.x );

        // The Y coordinate
        outputRow[ outputIndex++ ] = new Double( ep.y );

        // The measure
        outputRow[ outputIndex++ ] = new Double( 0.0 );

        // The Values in the DBF file...
        // PolyLimeM, point #"+data.shapeNr+", add dbf data";
        //
        Object[] dbfData = si.getDbfData();
        RowMetaInterface dbfMeta = si.getDbfMeta();
        for ( int d = 0; d < dbfMeta.size(); d++ ) {
          outputRow[ outputIndex++ ] = dbfData[ d ];
        }

        linesInput++;

        // Put it out to the rest of the world...
        try {
          putRow( data.outputRowMeta, data.outputRowMeta.cloneRow( outputRow ) );
        } catch ( KettleValueException e ) {
          throw new KettleStepException( "Unable to clone row", e );
        }
        break;
      default:
        System.out.println(
          "Unable to parse shape type [" + Shape.getEsriTypeDesc( si.getType() ) + "] : not yet implemented." );
        throw new KettleStepException(
          "Unable to parse shape type [" + Shape.getEsriTypeDesc( si.getType() ) + "] : not yet implemented." );
    }

    // Next shape please!
    data.shapeNr++;

    if ( ( linesInput % Const.ROWS_UPDATE ) == 0 ) {
      logBasic( "linenr " + linesInput );
    }

    return retval;
  }
@Test
  public void processRowPolyLineCloneRowTest() throws KettleException {
    shapePoint = mock( ShapePoint.class );
    shapePoint.x = 0;
    shapePoint.y = 0;

    shapePolyLine = mock( ShapePolyLine.class );
    shapePolyLine.nrparts = 0;
    shapePolyLine.nrpoints = 1;
    shapePolyLine.point = new ShapePoint[] { shapePoint };
    when( shapePolyLine.getType() ).thenReturn( Shape.SHAPE_TYPE_POLYLINE );
    when( shapePolyLine.getDbfData() ).thenReturn( new Object[] { new Object() } );
    when( shapePolyLine.getDbfMeta() ).thenReturn( rowMeta );

    shapeFileHeader = mock( ShapeFileHeader.class );
    when( shapeFileHeader.getShapeTypeDesc() ).thenReturn( "ShapeFileHeader Test" );

    shapeFile = mock( ShapeFile.class );
    when( shapeFile.getNrShapes() ).thenReturn( 1 );
    when( shapeFile.getShape( anyInt() ) ).thenReturn( shapePolyLine );
    when( shapeFile.getFileHeader() ).thenReturn( shapeFileHeader );

    shapeFileReaderData = new ShapeFileReaderData();
    shapeFileReaderData.outputRowMeta = rowMeta;
    shapeFileReaderData.shapeFile = shapeFile;
    shapeFileReaderData.shapeNr = 0;

    shapeFileReader = spy( createShapeFileReader() );
    shapeFileReader.first = false;

    Object[] outputRow = new Object[ RowDataUtil.allocateRowData( shapeFileReaderData.outputRowMeta.size() ).length ];
    try (
      MockedStatic<RowDataUtil> rowDataUtilMockedStatic = mockStatic( RowDataUtil.class, withSettings().lenient() ) ) {
      rowDataUtilMockedStatic.when( () -> RowDataUtil.allocateRowData( anyInt() ) ).thenReturn( outputRow );

      shapeFileReader.processRow( stepMockHelper.initStepMetaInterface, shapeFileReaderData );
      verify( shapeFileReader, times( 1 ) ).putRow( eq( shapeFileReaderData.outputRowMeta ), any( Object[].class ) );
      // Changing the original outputRow in order to test if the outputRow was cloned
      outputRow[ 0 ] = "outputRow Clone Test";
      verify( shapeFileReader, times( 0 ) ).putRow( shapeFileReaderData.outputRowMeta, outputRow );
    }
  }