views:

306

answers:

4

I'm translating programs of some language to nested java classes. At some point, the level of nesting becomes so deep that I get:

compiling Test.javaTest.java:5179: error while writing : Test$2...$1.class (File name too long)

where ... is a long string.

I'm using an ext3 filesystem, so i'm limited to 256 character long filenames. Also, I would like to keep on with this translation method (to inner classes) for the moment because I'm more interested in testing the language that in performing closure conversion, which would solve the problem. Is there a quick'n dirty way to bypass this? (using a different filesystem or telling javac to generate different filenames maybe?)

A: 

Comparison of File Systems, looks like ResierFS may be one of the only to support longer filenames. I'd be wary of this method as the all of the tools (javac, java, ant, ls, rm, cp, etc) might make assumptions about filename length since most filesystems are 255 and you'll be tied to one FS (what if it goes away?) If this is purely academic that reformat away (or use virtualization).

You may just need to reevalutate your algorithm to avoid nesting classes so deep. Can you use multiple files? I know you don't want to do it, but this may be the only option

basszero
+1  A: 

1 possible solution is to compile on another operating system, then use an Obfuscater such as yGuard. An obfuscater by default will change the class names to a minimal name (eg A, B, C...) thereby shortening the class name (and therefore file name) considerably.

Might be no use to you depending on exactly what you want to test, though.

Pool
+1  A: 

You can compile java from within java, sending the output to a file manager you implement yourself.

Use javax.tools.JavaCompiler, with a JavaFileManager that supplies you with the compiled output, which you could perhaps write directly to a jar?

Stephen Denne
+3  A: 

Bottom line is, yes, it's doable. One can change the name of an inner class, so it is shorter than the original name assigned by javac.

I was searching through The Java Language Specification and The Java Virtual Machine Specification to find where it talks about using the $ character to denote an inner class, and was not able to find a reference to it. The reason is, it doesn't really matter.

Case and point:

class A {
    class B {
     class C {}
    }

    A() {
     new B().new C();
    }

    public static void main(String[] s){
     new A();
    }
}

Here, we have nested inner classes. When compiled, we get the following files:

A.class
A$B.class
A$B$C.class

Here's a quick experiment:

  1. Open the A.class file, and change the reference to A$B$C and change it to ABCDE.
  2. Rename the A$B$C.class to ABCDE.class.
  3. Open the ABCDE.class, and change the reference to ABCDE.
  4. Run java A, see if it runs.

Note: The reason the A$B$C was changed to ABCDE is because the change in the length of the identifier seems to mangle the class file format, and will cause an error. Technical explanation will be at the end of this post.

Result? It works.

The reason is in the class file. Here's a disassembly of the original A.class, and only the relevant parts:

Compiled from "A.java"
class A extends java.lang.Object
  SourceFile: "A.java"
  InnerClass: 
   #10= #3 of #7; //B=class A$B of class A
   #22= #2 of #3; //C=class A$B$C of class A$B

// ... snip ... //

const #2 = class    #21; //  A$B$C

// ... snip ... //

const #21 = Asciz   A$B$C;

// ... snip ...//

Turns out, the name of the inner classes are just names in the constant pool.

If the name for the A$B$C class in the constant pool of A.class is changed to ABCDE, and if the A$B$C class' file name and name in the class file is changed, then the Java virtual machine will happy execute with the newly named inner class.

What does this mean?

One does not need to use the MyClass$1$1$1 ... $1 for the class name, but anything else that would suite one's need, therefore, it would be possible to have more combinations in a shorter file name.

How would someone go and do this? That I'll leave as an exercise to the reader.

Note on the use of ABCDE as the new class name

In this post, the name of the nested inner class, A$B$C was changed to ABCDE to keep the length of the class name the same, in order to prevent a ClassFormatError from being thrown. The reason for this is that the CONSTANT_Utf8_info structure of the constant pool has a length property which denotes the length of the string. I wasn't able to change the length as I was editing the class file in a text editor.

In order to shorten the string in the constant pool, I would presume that one would have to alter the value of the length field to reflect the length of the string itself.

Update

Yes, it is possible to edit the class file's constant pool to shorten the name of the inner class.

I was able to change the ABCDE class to Z class.

Here's a portion of the disassembly of the A.class:

Compiled from "A.java"
class A extends java.lang.Object
  SourceFile: "A.java"
  InnerClass: 
   #10= #3 of #7; //B=class A$B of class A
   #22= #2 of #3; //C=class Z of class A$B

// ... snip ...//

const #2 = class    #21; //  Z

// ... snip ...//

const #21 = Asciz   Z;

// ... snip ...//

As can be seen, the inner class is now referred to by Z, rather than A$B$C.

The change was performed by looking for the string A$B$C in the A.class and A$B$C.class files, and replacing it with Z, and changing the character before the string from the value 0x05 to 0x01, denoting that the length of the string is now 1 rather than 5.

With those changes, along with renaming the file to Z.class, the program ran as if nothing ever happened.

So, yes, it is possible to shorten the name of the inner class as well.

coobird
So... you're saying that it's possible to change the name of these classes, as long as the new name is the same length as the old name? I'm not sure how that will help the OP.
Andrzej Doyle
Please refer to the note at the bottom -- it should be possible to shorten it, if the constant pool's length information is altered.
coobird