public boolean evaluate( RowMetaInterface rowMeta, Object[] r ) {
    // Start of evaluate
    boolean retval = false;

    // If we have 0 items in the list, evaluate the current condition
    // Otherwise, evaluate all sub-conditions
    //
    try {
      if ( isAtomic() ) {

        if ( function == FUNC_TRUE ) {
          return !negate;
        }

        // Get fieldnrs left value
        //
        // Check out the fieldnrs if we don't have them...
        if ( leftValuename != null && leftValuename.length() > 0 ) {
          leftFieldnr = rowMeta.indexOfValue( leftValuename );
        }

        // Get fieldnrs right value
        //
        if ( rightValuename != null && rightValuename.length() > 0 ) {
          rightFieldnr = rowMeta.indexOfValue( rightValuename );
        }

        // Get fieldnrs left field
        ValueMetaInterface fieldMeta = null;
        Object field = null;
        if ( leftFieldnr >= 0 ) {
          fieldMeta = rowMeta.getValueMeta( leftFieldnr );
          field = r[ leftFieldnr ];
        } else {
          return false; // no fields to evaluate
        }

        // Get fieldnrs right exact
        ValueMetaInterface fieldMeta2 = rightExact != null ? rightExact.getValueMeta() : null;
        Object field2 = rightExact != null ? rightExact.getValueData() : null;
        if ( field2 == null && rightFieldnr >= 0 ) {
          fieldMeta2 = rowMeta.getValueMeta( rightFieldnr );
          field2 = r[ rightFieldnr ];
        }

        // Evaluate
        switch ( function ) {
          case FUNC_EQUAL:
            retval = ( fieldMeta.compare( field, fieldMeta2, field2 ) == 0 );
            break;
          case FUNC_NOT_EQUAL:
            retval = ( fieldMeta.compare( field, fieldMeta2, field2 ) != 0 );
            break;
          case FUNC_SMALLER:
            // Added this if/else to accommodate for CUST-270
            if ( "Y".equalsIgnoreCase( System.getProperty( Const.KETTLE_FILTER_TREAT_NULLS_AS_NOT_ZERO, "N" ) )
              && fieldMeta.isNull( field ) ) {
              retval = false;
            } else {
              retval = ( fieldMeta.compare( field, fieldMeta2, field2 ) < 0 );
            }
            break;
          case FUNC_SMALLER_EQUAL:
            // Added this if/else to accommodate for CUST-270
            if ( "Y".equalsIgnoreCase( System.getProperty( Const.KETTLE_FILTER_TREAT_NULLS_AS_NOT_ZERO, "N" ) )
              && fieldMeta.isNull( field ) ) {
              retval = false;
            } else {
              retval = ( fieldMeta.compare( field, fieldMeta2, field2 ) <= 0 );
            }
            break;
          case FUNC_LARGER:
            retval = ( fieldMeta.compare( field, fieldMeta2, field2 ) > 0 );
            break;
          case FUNC_LARGER_EQUAL:
            retval = ( fieldMeta.compare( field, fieldMeta2, field2 ) >= 0 );
            break;
          case FUNC_REGEXP:
            if ( fieldMeta.isNull( field ) || field2 == null ) {
              retval = false;
            } else {
              retval =
                Pattern
                  .matches( fieldMeta2.getCompatibleString( field2 ), fieldMeta.getCompatibleString( field ) );
            }
            break;
          case FUNC_NULL:
            retval = ( fieldMeta.isNull( field ) );
            break;
          case FUNC_NOT_NULL:
            retval = ( !fieldMeta.isNull( field ) );
            break;
          case FUNC_IN_LIST:
            // performance reason: create the array first or again when it is against a field and not a constant
            //
            if ( inList == null || rightFieldnr >= 0 ) {
              inList = Const.splitString( fieldMeta2.getString( field2 ), ';', true );
              for ( int i = 0; i < inList.length; i++ ) {
                inList[i] = inList[i] == null ? null : inList[i].replace( "\\", "" );
              }
              Arrays.sort( inList );
            }
            String searchString = fieldMeta.getCompatibleString( field );
            int inIndex = -1;
            if ( searchString != null ) {
              inIndex = Arrays.binarySearch( inList, searchString );
            }
            retval = inIndex >= 0;
            break;
          case FUNC_CONTAINS:
            String fm2CompatibleContains = fieldMeta2.getCompatibleString( field2 );
            retval = Optional.ofNullable( fieldMeta.getCompatibleString( field ) )
              .filter( s -> s.contains( fm2CompatibleContains ) ).isPresent();
            break;
          case FUNC_STARTS_WITH:
            String fm2CompatibleStarts = fieldMeta2.getCompatibleString( field2 );
            retval = Optional.ofNullable( fieldMeta.getCompatibleString( field ) )
              .filter( s -> s.startsWith( fm2CompatibleStarts ) ).isPresent();
            break;
          case FUNC_ENDS_WITH:
            String string = fieldMeta.getCompatibleString( field );
            if ( !Utils.isEmpty( string ) ) {
              if ( rightString == null && field2 != null ) {
                rightString = fieldMeta2.getCompatibleString( field2 );
              }
              if ( rightString != null ) {
                retval = string.endsWith( fieldMeta2.getCompatibleString( field2 ) );
              } else {
                retval = false;
              }
            } else {
              retval = false;
            }
            break;
          case FUNC_LIKE:
            // Converts to a regular expression
            // TODO: optimize the patterns and String replacements
            //
            if ( fieldMeta.isNull( field ) || field2 == null ) {
              retval = false;
            } else {
              String regex = fieldMeta2.getCompatibleString( field2 );
              regex = regex.replace( "%", ".*" );
              regex = regex.replace( "?", "." );
              retval = Pattern.matches( regex, fieldMeta.getCompatibleString( field ) );
            }
            break;
          default:
            break;
        }

        // Only NOT makes sense, the rest doesn't, so ignore!!!!
        // Optionally negate
        //
        if ( isNegated() ) {
          retval = !retval;
        }
      } else {
        // Composite : get first
        Condition cb0 = list.get( 0 );
        retval = cb0.evaluate( rowMeta, r );

        // Loop over the conditions listed below.
        //
        for ( int i = 1; i < list.size(); i++ ) {
          // Composite : #i
          // Get right hand condition
          Condition cb = list.get( i );

          // Evaluate the right hand side of the condition cb.evaluate() within
          // the switch statement
          // because the condition may be short-circuited due to the left hand
          // side (retval)
          switch ( cb.getOperator() ) {
            case Condition.OPERATOR_OR:
              retval = retval || cb.evaluate( rowMeta, r );
              break;
            case Condition.OPERATOR_AND:
              retval = retval && cb.evaluate( rowMeta, r );
              break;
            case Condition.OPERATOR_OR_NOT:
              retval = retval || ( !cb.evaluate( rowMeta, r ) );
              break;
            case Condition.OPERATOR_AND_NOT:
              retval = retval && ( !cb.evaluate( rowMeta, r ) );
              break;
            case Condition.OPERATOR_XOR:
              retval = retval ^ cb.evaluate( rowMeta, r );
              break;
            default:
              break;
          }
        }

        // Composite: optionally negate
        if ( isNegated() ) {
          retval = !retval;
        }
      }
    } catch ( Exception e ) {
      throw new RuntimeException( "Unexpected error evaluation condition [" + toString() + "]", e );
    }

    return retval;
  }
@Test
  public void testNullSmallerThanZero() {
    String left = "left";
    String right = "right";

    Long leftValue = null;
    Long rightValue = 0L;

    RowMetaInterface rowMeta = new RowMeta();
    rowMeta.addValueMeta( new ValueMetaInteger( left ) );
    rowMeta.addValueMeta( new ValueMetaInteger( right ) );

    Condition condition = new Condition( left, Condition.FUNC_SMALLER, right, null );
    assertTrue( condition.evaluate( rowMeta, new Object[] { leftValue, rightValue  } ) );
  }