How to correct a mess java .class file set or generate a proper .jar archive from a mess .class set?

前端 未结 1 935
终归单人心
终归单人心 2021-01-17 01:43

Background

I have to contact different kind of Java project with various of build system. Sometimes the directory structure is different from the package hierarchy.

相关标签:
1条回答
  • 2021-01-17 02:21

    Generally, it is better to fix the build system issues, to generate the correct directory structure in the first place, rather than trying to fix it after the fact. One problem I see, is that classes from different packages may have the same simple name, so if their class files are written to the same flat directory, one of them will overwrite the other and this data loss can not be fixed afterwards.

    Generally, the constant pool at the beginning of the class file contains the qualified class name, so it is possible to extract it, but you need to understand the class file structure to pick the right string. The following method will parse a class file and extract the name (in its internal form):

    static String getClassName(ByteBuffer buf) {
        if(buf.order(ByteOrder.BIG_ENDIAN).getInt()!=0xCAFEBABE) {
            throw new IllegalArgumentException("not a valid class file");
        }
        int minor=buf.getChar(), ver=buf.getChar(), poolSize=buf.getChar();
        int[] pool = new int[poolSize];
        //System.out.println("version "+ver+'.'+minor);
        for(int ix=1; ix<poolSize; ix++) {
            String s; int index1=-1, index2=-1;
            byte tag = buf.get();
            switch(tag) {
                default: throw new UnsupportedOperationException(
                        "unknown pool item type "+buf.get(buf.position()-1));
                case CONSTANT_Utf8:
                    buf.position((pool[ix]=buf.position())+buf.getChar()+2); continue;
                case CONSTANT_Module: case CONSTANT_Package: case CONSTANT_Class:
                case CONSTANT_String: case CONSTANT_MethodType:
                    pool[ix]=buf.getChar(); break;
                case CONSTANT_FieldRef: case CONSTANT_MethodRef:
                case CONSTANT_InterfaceMethodRef: case CONSTANT_NameAndType:
                case CONSTANT_InvokeDynamic: case CONSTANT_Dynamic:
                case CONSTANT_Integer: case CONSTANT_Float:
                    buf.position(buf.position()+4); break;
                case CONSTANT_Double: case CONSTANT_Long:
                    buf.position(buf.position()+8); ix++; break;
                case CONSTANT_MethodHandle: buf.position(buf.position()+3); break;
            }
        }
        int access = buf.getChar(), thisClass = buf.getChar();
        buf.position(pool[pool[thisClass]]);
        return decodeString(buf);
    }
    private static String decodeString(ByteBuffer buf) {
        int size=buf.getChar(), oldLimit=buf.limit();
        buf.limit(buf.position()+size);
        StringBuilder sb=new StringBuilder(size+(size>>1));
        while(buf.hasRemaining()) {
            byte b=buf.get();
            if(b>0) sb.append((char)b);
            else {
                int b2 = buf.get();
                if((b&0xf0)!=0xe0)
                    sb.append((char)((b&0x1F)<<6 | b2&0x3F));
                else {
                    int b3 = buf.get();
                    sb.append((char)((b&0x0F)<<12 | (b2&0x3F)<<6 | b3&0x3F));
                }
            }
        }
        buf.limit(oldLimit);
        return sb.toString();
    }
    private static final byte CONSTANT_Utf8 = 1, CONSTANT_Integer = 3,
        CONSTANT_Float = 4, CONSTANT_Long = 5, CONSTANT_Double = 6,
        CONSTANT_Class = 7, CONSTANT_String = 8, CONSTANT_FieldRef = 9,
        CONSTANT_MethodRef = 10, CONSTANT_InterfaceMethodRef = 11,
        CONSTANT_NameAndType = 12, CONSTANT_MethodHandle = 15,
        CONSTANT_MethodType = 16, CONSTANT_Dynamic = 17, CONSTANT_InvokeDynamic = 18,
        CONSTANT_Module = 19, CONSTANT_Package = 20;
    

    This can be used to fix a wrong file location like this:

    static void checkAndMoveClassFile(Path path) throws IOException {
        ByteBuffer bb;
        try(FileChannel ch=FileChannel.open(path, StandardOpenOption.READ)) {
            bb=ByteBuffer.allocate((int)ch.size());
            while(bb.hasRemaining()) ch.read(bb);
            bb.flip();
        }
        String name = getClassName(bb);
        Path newPath = path.resolveSibling(name+".class");
        if(!path.equals(newPath)) {
            System.out.println("moving "+path+" to "+newPath);
            Files.createDirectories(newPath.getParent());
            Files.move(path, newPath);
        }
    }
    

    which you can run over a directory easily

    Files.list(dirPath)
         .filter(p -> p.getFileName().toString().endsWith(".class"))
         .forEach(p -> {
             try { checkAndMoveClassFile(p); }
             catch (IOException ex) { throw new UncheckedIOException(ex); }
         });
    
    0 讨论(0)
提交回复
热议问题