27

Specifically, I'm trying to create a unit test for a method which requires uses File.separatorChar to build paths on windows and unix. The code must run on both platforms, and yet I get errors with JUnit when I attempt to change this static final field.

Anyone have any idea what's going on?

Field field = java.io.File.class.getDeclaredField( "separatorChar" );
field.setAccessible(true);
field.setChar(java.io.File.class,'/');

When I do this, I get

IllegalAccessException: Can not set static final char field java.io.File.separatorChar to java.lang.Character

Thoughts?

Stefan Kendall
  • 66,414
  • 68
  • 253
  • 406
  • 1
    It might be better to run the unit tests in a VirtualBox environment under the other target OS as well. Who knows what will break when you mess with the JVM like that. Also, maybe it is possible to rewrite your code to not use File.separatorChar directly. You can build paths using the File(parentFile, name) constructor, for example. – Thilo Mar 19 '10 at 02:33
  • @Thilo: That's a good idea, and now that I think about it, there's probably a way to run my logic in a cross-platform manner without dealing with the file URLs at all. I think knowing how to change java.io.File.separatorChar, however, is a useful thing to know for some other legitimate use case. – Stefan Kendall Mar 19 '10 at 03:20

7 Answers7

68

From the documentation for Field.set:

If the underlying field is final, the method throws an IllegalAccessException unless setAccessible(true) has succeeded for this field and this field is non-static.

So at first it seems that you are out of luck, since File.separatorChar is static. Surprisingly, there is a way to get around this: simply make the static field no longer final through reflection.

I adapted this solution from javaspecialist.eu:

static void setFinalStatic(Field field, Object newValue) throws Exception {
    field.setAccessible(true);

    // remove final modifier from field
    Field modifiersField = Field.class.getDeclaredField("modifiers");
    modifiersField.setAccessible(true);
    modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL);

    field.set(null, newValue);
}

I've tested it and it works:

setFinalStatic(File.class.getField("separatorChar"), '#');
System.out.println(File.separatorChar); // prints "#"

Do exercise extreme caution with this technique. Devastating consequences aside, the following actually works:

setFinalStatic(Boolean.class.getField("FALSE"), true);
System.out.format("Everything is %s", false); // "Everything is true"

Important update: the above solution does not work in all cases. If the field is made accessible and read through Reflection before it gets reset, an IllegalAccessException is thrown. It fails because the Reflection API creates internal FieldAccessor objects which are cached and reused (see the java.lang.reflect.Field#acquireFieldAccessor(boolean) implementation). Example test code which fails:

Field f = File.class.getField("separatorChar"); f.setAccessible(true); f.get(null);
// call setFinalStatic as before: throws IllegalAccessException
Rogério
  • 16,171
  • 2
  • 50
  • 63
polygenelubricants
  • 376,812
  • 128
  • 561
  • 623
  • 17
    #define FALSE 1 is back, and it's ready to kick some ass all over again. – Stefan Kendall Mar 19 '10 at 02:24
  • 3
    Holy crap! The introduction of setAccessible is a major screw up. – Lawrence Dol Mar 19 '10 at 03:14
  • 6
    @NateS: no one is saying that this is a good idea. We were investigating if something is doable. Apparently it is. That's not a reason to downvote me. Don't shoot the messenger. If you have a problem with WHY we're doing this, downvote OP's question. I'm only showing HOW. – polygenelubricants Mar 19 '10 at 06:35
  • 2
    Mouse over on down vote says, "This answer is not useful". Sorry, but I feel your answer meets this criteria. Your answer makes it appear that it is possible to change final values when in fact it is not. The inlined values will not be changed. The results are totally unpredicable. I appreciate the sharing of such esoteric knowledge, but doing what you suggest should never, ever be done. – NateS Mar 19 '10 at 08:15
  • 3
    @NateS `static final` *compile-time constants* can be inlined by javac. Obviously that isn't the case here. This is an attempt to test something that might change. Whilst at runtime `final` fields can be inlined, in practice that should not be a problem for unit tests. – Tom Hawtin - tackline Mar 20 '10 at 04:24
  • 1
    @Tom Hastin - tackline, the static final constant File.separator can be inlined by the JIT. I don't see how undefined behavior "should not be a problem for unit tests" when it may or may not be testing what you think it is. – NateS Mar 24 '10 at 07:21
  • JLS 17.5.3 details when it is OK to modify final fields and the associated problems: http://java.sun.com/docs/books/jls/third_edition/html/memory.html#17.5.3 – NateS Apr 06 '10 at 06:28
  • Whoops, upvoted NateS by accident. NateS, I don't agree. The answer is interesting and completely dangerous, and was presented as such. – James Moore Sep 14 '11 at 15:43
  • 2
    Doesnt seem to work for me. Field modifiersField = Field.class.getDeclaredField("modifiers"); Throws Exception: java.lang.NoSuchFieldException: modifiers – Bamerza Feb 19 '14 at 02:03
  • 1
    This is great and very useful if used in certain situations. (E.g. my use case is a unit test reducing a very high paging limit constant.) But I'm wondering if the same exists in a commonly used library somewhere - e.g. Guava, Apache Commons or Spring. The nearest I've found is Spring's [`ReflectionTestUtils`](http://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/test/util/ReflectionTestUtils.html) - but unfortunately this only provides `setField` methods for fields on object instances, not static fields. – Steve Chambers Dec 16 '16 at 11:48
2

Try invoking on an instance of file not on an instance of class File

E.g.

File file = ...;    
field.setChar(file,'/');

You could also try http://code.google.com/p/jmockit/ and mock the static method FileSystem.getFileSystem(). (don't know if you can mock static variables, normally those hacks shouldn't be necessary -> write oo code and use 'only' mockito)

Karussell
  • 17,085
  • 16
  • 97
  • 197
2

Just use / everywhere when constructing Files. I've been doing that for 13 years and never had a problem. Nothing to test either.

user207421
  • 305,947
  • 44
  • 307
  • 483
1

I realise this doesn't answer your question directly, but Apache Commons FileNameUtils will do cross-platform filename construction, and may save you writing your own class to do this.

Brian Agnew
  • 268,207
  • 37
  • 334
  • 440
  • I'm deconstructing a path, rather than constructing, but what I could do is convert everything to unix separators, run my algorithm, and then convert to system separators. In this manner, I can test without needing to mock java.io.File. – Stefan Kendall Mar 19 '10 at 00:47
  • I wound up using this solution, but the answer below answered my initial question, so in the spirit of stackoverflow, I accepted the other answer. – Stefan Kendall Mar 19 '10 at 01:50
1

here I am going to set value for "android.os.Build.VERSION.RELEASE", where VERSION is the class name and RELEASE is the final static string value.

If the underlying field is final, the method throws an IllegalAccessException so that we need to use setAccessible(true) , NoSuchFieldException needs to be added when you use field.set() method

@RunWith(PowerMockRunner.class)
@PrepareForTest({Build.VERSION.class})
public class RuntimePermissionUtilsTest {
@Test
public void hasStoragePermissions() throws IllegalAccessException, NoSuchFieldException {
    Field field = Build.VERSION.class.getField("RELEASE");
    field.setAccessible(true);
    field.set(null,"Marshmallow");
 }
}

now the value of String RELEASE will return "Marshmallow".

anand krish
  • 4,281
  • 4
  • 44
  • 47
0

Instead of using File.separatorChar declare your service Class, let's call it PathBuilder or something. This class will have a concatPaths() method which will concatenate the two parameters (using the OS's separator char). The beauty is that you are writing this class so you can tweak it anyway you want when you unit test it.

Itay Maman
  • 30,277
  • 10
  • 88
  • 118
0

You can take the source for java.io.File, and modify it so that separatorChar and separator are not final, and add a setSeparatorChar method that updates the two of them, then include the compiled class in your bootclasspath.

Stephen Denne
  • 36,219
  • 10
  • 45
  • 60
  • You may have to extend that ability to all java.io.FileSystem implementations. – Stephen Denne Mar 19 '10 at 00:19
  • Note: Applications that use this option for the purpose of overriding a class in rt.jar should not be deployed as doing so would contravene the Java 2 Runtime Environment binary code license. – Stephen Denne Mar 19 '10 at 00:21