3

I have an application where I am generating a "target file" based on a Java "source" class. I want to regenerate the target when the source changes. I have decided the best way to do this would be to get a byte[] of the class contents and calculate a checksum on the byte[].

I am looking for the best way to get the byte[] for a class. This byte[] would be equivalent to the contents of the compiled .class file. Using ObjectOutputStream does not work. The code below generates a byte[] that is much smaller than the byte contents of the class file.

// Incorrect function to calculate the byte[] contents of a Java class
public static final byte[] getClassContents(Class<?> myClass) throws IOException {
    ByteArrayOutputStream buffer = new ByteArrayOutputStream();
    try( ObjectOutputStream stream = new ObjectOutputStream(buffer) ) {
        stream.writeObject(myClass);
    }
    // This byte array is much smaller than the contents of the *.class file!!!
    byte[] contents = buffer.toByteArray();
    return contents;
}

Is there a way to get the byte[] with the identical contents of the *.class file? Calculating the checksum is the easy part, the hard part is obtaining the byte[] contents used to calculate an MD5 or CRC32 checksum.

user2994892
  • 71
  • 1
  • 7
  • This sounds like something that you really probably shouldn't be rolling your own thing for. Why do you want the raw .class file? – torquestomp Nov 15 '13 at 05:05
  • I don't understand why you want to check the byte[] of the .class file, instead of the src file. You can also try checking the last modified time. – Keerthivasan Nov 15 '13 at 05:08
  • @Keerthi, I want to calculate the contents of the *.class rather than *.java file because the latter will change even if comments change. I don't care if the contents of the comments change, only if the actual instructions change. – user2994892 Nov 15 '13 at 05:22
  • @torquestomp, I want the raw *.class file because it represents the full contents of both the implementation and the signatures. I want my target file to be regenerated only if the checksum of the *.class file changed since the last time the source was generated. – user2994892 Nov 15 '13 at 05:24
  • MakeFile concept is also kind of a technique used. Try to get some idea from it. But it again checks for any changes of source file by comparing timestamps of resultant binary/object file and the last modified type of its source file from its internal database. http://pubs.opengroup.org/onlinepubs/9699919799/utilities/make.html – Chand Priyankara Nov 15 '13 at 05:32
  • I think best way would be adding isModified to method to class and every time the setters of the class are called with new value, the isModified returns true. – Anssi Aug 28 '17 at 09:52

3 Answers3

4

THis is the solution that I ended up using. I don't know if it's the most efficient implementation, but the following code uses the class loader to get the location of the *.class file and reads its contents. For simplicity, I skipped buffering of the read.

// Function to obtain the byte[] contents of a Java class
public static final byte[] getClassContents(Class<?> myClass) throws IOException {
    String path = myClass.getName().replace('.', '/');
    String fileName = new StringBuffer(path).append(".class").toString();
    URL url = myClass.getClassLoader().getResource(fileName);
    ByteArrayOutputStream buffer = new ByteArrayOutputStream();
    try (InputStream stream = url.openConnection().getInputStream()) {
        int datum = stream.read();
        while( datum != -1) {
            buffer.write(datum);
            datum = stream.read();
        }
    }   
    return buffer.toByteArray();
}
user2994892
  • 71
  • 1
  • 7
0

I don't get what you means, but i think you are looking for this, MD5.

To check MD5 of a file, you can use this code

public String getMd5(File file)
{
    DigestInputStream stream = null;
    try
    {
        stream = new DigestInputStream(new FileInputStream(file), MessageDigest.getInstance("MD5"));
        byte[] buffer = new byte[65536];

        read = stream.read(buffer);
        while (read >= 1) {
        read = stream.read(buffer);
        }
    }
    catch (Exception ignored)
    {
        int read;
        return null;
    }
    return String.format("%1$032x", new Object[] { new BigInteger(1, stream.getMessageDigest().digest()) });
}

Then, you can store the md5 of a file in any way for exmaple XML. An exmaple of MD5 is 49e6d7e2967d1a471341335c49f46c6c so once the file name and size change, md5 will change. You can store md5 of each file in XML format and next time your run a code to check md5 and compare the md5 of each file in the xml file.

Jeremy
  • 641
  • 1
  • 7
  • 15
  • 1
    My question is not really about calculating the checksum either by MD5 or other means. The question is about obtaining the byte[] contents used to derive the checksum. – user2994892 Nov 15 '13 at 05:26
  • May be this link can help http://stackoverflow.com/questions/858980/file-to-byte-in-java – Keerthivasan Nov 15 '13 at 05:50
  • 1
    @user2994892 you should really update the question subject then – eis Nov 15 '13 at 07:33
0

If you really want the contents of the .class file, you should read the contents of .class file, not the byte[] representation that is in memory. So something like

import java.io.*;

public class ReadSelf {
    public static void main(String args[]) throws Exception  {
        Class classInstance = ReadSelf.class;
        byte[] bytes = readClass(classInstance);
    }
    public static byte[] readClass(Class classInstance) throws Exception {
        String name = classInstance.getName();
        name = name.replaceAll("[.]", "/") + ".class";
        System.out.println("Reading this: " + name);
        File file = new File(name);
        System.out.println("exists: " + file.exists());
        return read(file);
    }
    public static byte[] read(File file) throws Exception {
        byte[] data = new byte[(int)file.length()]; // can only read a file of size INT_MAX
        DataInputStream inputStream =
        new DataInputStream(
            new BufferedInputStream(
                new FileInputStream(file)));

        int total = 0;
        int nRead = 0;
        try {
            while((nRead = inputStream.read(data)) != -1) {
                total += nRead;
            }
        }
        finally {
            inputStream.close();
        }
        System.out.println("Read " + total
            + " characters, which should match file length of "
            + file.length() + " characters");
        return data;
    }
}
eis
  • 51,991
  • 13
  • 150
  • 199
  • You are correct that I want the direct contents of the *.class file and reading it from the file system is the best way to do this. I am posting the solution I found as well. – user2994892 Nov 15 '13 at 14:49