@VisibleForTesting
  Class<?> cookClass( UserDefinedJavaClassDef def, ClassLoader clsloader ) throws CompileException, IOException, RuntimeException, KettleStepException {

    String checksum = def.getChecksum();
    Class<?> rtn = UserDefinedJavaClassMeta.classCache.getIfPresent( checksum );
    if ( rtn != null ) {
      return rtn;
    }

    if ( Thread.currentThread().getContextClassLoader() == null ) {
      Thread.currentThread().setContextClassLoader( this.getClass().getClassLoader() );
    }

    ClassBodyEvaluator cbe = new ClassBodyEvaluator();
    if ( clsloader == null ) {
      cbe.setParentClassLoader( Thread.currentThread().getContextClassLoader() );
    } else {
      cbe.setParentClassLoader( clsloader );
    }

    cbe.setClassName( def.getClassName() );

    StringReader sr;
    if ( def.isTransformClass() ) {
      cbe.setExtendedType( TransformClassBase.class );
      sr = new StringReader( def.getTransformedSource() );
    } else {
      sr = new StringReader( def.getSource() );
    }

    cbe.setDefaultImports( new String[] {
      "org.pentaho.di.trans.steps.userdefinedjavaclass.*", "org.pentaho.di.trans.step.*",
      "org.pentaho.di.core.row.*", "org.pentaho.di.core.*", "org.pentaho.di.core.exception.*" } );

    cbe.cook( new Scanner( null, sr ) );
    rtn = cbe.getClazz();
    UserDefinedJavaClassMeta.classCache.put( checksum, rtn );
    return rtn;
  }
@Test
  public void cookClassesCachingTest() throws Exception {
    String codeBlock1 = "public boolean processRow() {\n"
        + "    return true;\n"
        + "}\n\n";
    String codeBlock2 = "public boolean processRow() {\n"
        + "    // Random comment\n"
        + "    return true;\n"
        + "}\n\n";
    UserDefinedJavaClassMeta userDefinedJavaClassMeta1 = new UserDefinedJavaClassMeta();

    UserDefinedJavaClassDef userDefinedJavaClassDef1 = new UserDefinedJavaClassDef( UserDefinedJavaClassDef.ClassType.NORMAL_CLASS, "MainClass", codeBlock1 );

    StepMeta stepMeta = Mockito.mock( StepMeta.class );
    Mockito.when( stepMeta.getName() ).thenReturn( "User Defined Java Class" );
    userDefinedJavaClassMeta1.setParentStepMeta( stepMeta );

    UserDefinedJavaClassMeta userDefinedJavaClassMetaSpy = Mockito.spy( userDefinedJavaClassMeta1 );

    // Added classloader for https://jira.pentaho.com/browse/PDI-44134
    Class<?> clazz1 = userDefinedJavaClassMetaSpy.cookClass( userDefinedJavaClassDef1, null );
    Class<?> clazz2 = userDefinedJavaClassMetaSpy.cookClass( userDefinedJavaClassDef1, clazz1.getClassLoader() );
    Assert.assertTrue( clazz1 == clazz2 ); // Caching should work here and return exact same class

    UserDefinedJavaClassMeta userDefinedJavaClassMeta2 = new UserDefinedJavaClassMeta();
    UserDefinedJavaClassDef userDefinedJavaClassDef2 = new UserDefinedJavaClassDef( UserDefinedJavaClassDef.ClassType.NORMAL_CLASS, "AnotherClass", codeBlock2 );

    StepMeta stepMeta2 = Mockito.mock( StepMeta.class );
    Mockito.when( stepMeta2.getName() ).thenReturn( "Another UDJC" );
    userDefinedJavaClassMeta2.setParentStepMeta( stepMeta2 );
    UserDefinedJavaClassMeta userDefinedJavaClassMeta2Spy = Mockito.spy( userDefinedJavaClassMeta2 );

    Class<?> clazz3 = userDefinedJavaClassMeta2Spy.cookClass( userDefinedJavaClassDef2, clazz2.getClassLoader() );

    Assert.assertTrue( clazz3 != clazz1 ); // They should not be the exact same class
  }