Java – Remove final modifier from 3rd party classes

Remove final modifier from 3rd party classes… here is a solution to the problem.

Remove final modifier from 3rd party classes

I need to test write a JUnit test to test the following line:

CSVRecord csvRecord = csvReader.readCsv(filename);

CSVRecord from org.apache.commons.csv is the final class. If I try to test this with EasyMock, I get the following error:

java.lang.IllegalArgumentException: Cannot subclass final class pathname. FinalClass
at org.easymock.cglib.proxy.Enhancer.generateClass(Enhancer.java:565)
at org.easymock.cglib.core.DefaultGeneratorStrategy.generate(DefaultGeneratorStrategy.java:25)
at ...

So I need to separate the “final” modifier from CSVRecord. I tried it with javassist. However, I encountered an error. Take a look at this minimalist example:

public class MyTestClass extends EasyMockSupport {

@Mock
    private MockedClass mockedClass;

@TestSubject
    private MyClass classUnderTest = new AmountConverter();

@Test
    public void testName() throws Exception {
        ClassPool pool = ClassPool.getDefault();
        CtClass ctClass = pool.get(FinalClass.class.getName());
        ctClass.defrost();
        removeFinal(ctClass);
        FinalClass finalClass = (FinalClass) EasyMock.createMock(ctClass.toClass());
        expect(mockedClass.foo()).andReturn(finalClass);

replayAll();

classUnderTest.foo();
    }

static void removeFinal(CtClass clazz) throws Exception {
        int modifiers = clazz.getModifiers();
        if(Modifier.isFinal(modifiers)) {
            System.out.println("Removing Final");
            int notFinalModifier = Modifier.clear(modifiers, Modifier.FINAL);
            clazz.setModifiers(notFinalModifier);
        }
    }
}

with

public class MyClass {

@Inject
    private MockedClass mockedClass;

public void foo() {
        mockedClass.foo();
    }

class MockedClass {

FinalClass foo() {
            return null;
        }

}
}

In its own class file

public final class FinalClass {

}

The following error occurred

javassist. CannotCompileException: by java.lang.LinkageError: loader (instance of  sun/misc/Launcher$AppClassLoader): attempted  duplicate class definition for name: "pathname/FinalClass"
at javassist. ClassPool.toClass(ClassPool.java:1099)
at javassist. ClassPool.toClass(ClassPool.java:1042)
at javassist. ClassPool.toClass(ClassPool.java:1000)
at javassist. CtClass.toClass(CtClass.java:1224)
...

Solution

You cannot change the definition of a loaded class in this way.

The problem is constructing FinalClass.class.getName() or more specifically, the class literal FinalClass.class which has indeed loaded the class to generate the associated Class object, loading the runtime representation of the class.

Assuming you haven’t used the class in any other way before that, you can simply change the code to

ClassPool pool = ClassPool.getDefault();
CtClass ctClass = pool.get("qualified.name.of.FinalClass");
ctClass.defrost();
removeFinal(ctClass);
FinalClass finalClass = (FinalClass) EasyMock.createMock(ctClass.toClass());

Change the definition of a class before creating a runtime representation.

Related Problems and Solutions