How do I get the last modification time of a Java resource?
Asked Answered
N

5

15

Can someone please tell me a reliable way to get the last modification time of a Java resource? The resource can be a file or an entry in a JAR.

Nocturne answered 13/1, 2010 at 14:36 Comment(3)
I didn't even know JARs could have timestamps in them, but given it is like ZIP, I'm not entirely surprised. Interesting.Kurtkurth
@Amigable Clark Kant - just a note, but the JAR format will get an update with Java 7, so assumptions about it being a ZIP file may no longer hold.Pelayo
They still hold in Java 8 :)Faerie
P
20

If you with "resource" mean something reachable through Class#getResource or ClassLoader#getResource, you can get the last modified time stamp through the URLConnection:

URL url = Test.class.getResource("/org/jdom/Attribute.class");
System.out.println(new Date(url.openConnection().getLastModified()));

Be aware that getLastModified() returns 0 if last modified is unknown, which unfortunately is impossible to distinguish from a real timestamp reading "January 1st, 1970, 0:00 UTC".

Pean answered 13/1, 2010 at 14:44 Comment(7)
Works in Swing, but not in Android. In Android I always get 0, although the accessible jarfile has a non-zero last modified date.Priedieu
@j4nbur53: The JAR file you are compiling against? That one is not available at run time. No idea if the time stamp is retained in the Android APK file. Even if the language is similar, the Android runtime is very different from the Java runtime.Pean
The APK file on Android is also a JAR file, you get URLs to entries by a simple getResource() call. But the implementation on Android differes from the one on Swing for example. The JarURLConnection does not implement getHeader() by delegating to the underlying jar file so that in the end annoyingly it doesn't work on Android. (One can inspect both sources and find the difference in implementation)Priedieu
"which unfortunately is impossible to distinguish from a real timestamp" Unless you got some very old files lying around I don't think it will be hard to distinguish between real last-modified dates and Jan. 1 1970 :)Faerie
@StijndeWitt It's just silly API design to use one otherwise meaningful value to encode the special meaning 'not available'. The method should have returned a java.util.Date if the last modified timestamp is known, otherwise null.Pean
@Pean True. But in practice it is rarely a problem. One advantage though... This magic EPOCH date allows me to fill in 1/1/1970 as birthdate on websites that I don't want to give my personal details, safely in the knowledge that in the DB there will be just the value 0 :)Faerie
be aware that this returns the time the jar file was changed, not necessarily the modification time of the class file (at least with windows 7, Java 8)Salian
B
6

The problem with url.openConnection().getLastModified() is that getLastModified() on a FileURLConnection creates an InputStream to that file. So you have to call urlConnection.getInputStream().close() after getting last modified date. In contrast JarURLConnection creates the input stream when calling getInputStream().

Boulder answered 10/6, 2010 at 13:36 Comment(2)
so what is more optimized leaving it open or calling getinputstream and then close?Gowrie
@Gowrie Leaving a stream open always leads to a resource leak. You can only optimize by using api that does not open any stream.Boulder
W
5

Apache Commons VFS provides a generic way of interacting with files from different sources. FileObject.getContent() returns a FileContent object which has a method for retreiving the last modified time.

Here is a modified example from the VFS website:

import org.apache.commons.vfs.FileSystemManager;
import org.apache.commons.vfs.FileObject;
...
FileSystemManager fsManager = VFS.getManager();
FileObject jarFile = fsManager.resolveFile( "jar:lib/aJarFile.jar" );
System.out.println( jarFile.getName().getBaseName() + " " + jarFile.getContent().getLastModifiedTime() );

// List the children of the Jar file
FileObject[] children = jarFile.getChildren();
System.out.println( "Children of " + jarFile.getName().getURI() );
for ( int i = 0; i < children.length; i++ )
{
    System.out.println( children[ i ].getName().getBaseName() + " " + children[ i ].getContent().getLastModifiedTime());
}
Wallflower answered 13/1, 2010 at 14:49 Comment(1)
sounds overly complicated. I wound up using the java zip api then using the last modified. I also iterated through them and then compared the most likely non modified stamp in a modified jar.Gowrie
P
3

I am currently using the following solution. The solution is the same like most of the other solutions in its initial steps, namely some getResource() and then openConnection().

But when I have the connection I am using the following code:

/**
 * <p>Retrieve the last modified date of the connection.</p>
 *
 * @param con The connection.
 * @return The last modified date.
 * @throws IOException Shit happens.
 */
private static long getLastModified(URLConnection con) throws IOException {
    if (con instanceof JarURLConnection) {
        return ((JarURLConnection)con).getJarEntry().getTime();
    } else {
        return con.getLastModified();
    }
}

The above code works on android and non-android, it returns the last modified date of the entry inside the ZIP if the resources is found inside an archive, otherwise it returns what it gets from the connection.

Bye

P.S.: The code still needs some brushing, there are some border cases where getJarEntry() is null.

Priedieu answered 26/10, 2014 at 18:36 Comment(0)
D
1

Here is my code for getting the last modification time of your JAR file or compiled class (when using an IDE).

import java.io.File;
import java.io.IOException;
import java.net.URISyntaxException;
import java.net.URL;
import java.nio.file.attribute.FileTime;
import java.text.DateFormat;
import java.util.Date;
import java.util.Enumeration;
import java.util.Locale;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.zip.ZipEntry;

public class ProgramBuildTime
{
    private static String getJarName()
    {
        Class<?> currentClass = getCurrentClass();
        return new File(currentClass.getProtectionDomain()
                .getCodeSource()
                .getLocation()
                .getPath())
                .getName();
    }

    private static Class<?> getCurrentClass()
    {
        return new Object() {}.getClass().getEnclosingClass();
    }

    private static boolean runningFromJAR()
    {
        String jarName = getJarName();
        return jarName.endsWith(".jar");
    }

    public static String getLastModifiedDate() throws IOException, URISyntaxException
    {
        Date date;

        if (runningFromJAR())
        {
            String jarFilePath = getJarName();
            try (JarFile jarFile = new JarFile(jarFilePath))
            {
                long lastModifiedDate = 0;

                for (Enumeration<JarEntry> entries = jarFile.entries(); entries.hasMoreElements(); )
                {
                    String element = entries.nextElement().toString();
                    ZipEntry entry = jarFile.getEntry(element);
                    FileTime fileTime = entry.getLastModifiedTime();
                    long time = fileTime.toMillis();

                    if (time > lastModifiedDate)
                    {
                        lastModifiedDate = time;
                    }
                }

                date = new Date(lastModifiedDate);
            }
        } else
        {
            Class<?> currentClass = getCurrentClass();
            URL resource = currentClass.getResource(currentClass.getSimpleName() + ".class");

            switch (resource.getProtocol())
            {
                case "file":
                    date = new Date(new File(resource.toURI()).lastModified());
                    break;

                default:
                    throw new IllegalStateException("No matching protocol found!");
            }
        }

        DateFormat dateFormat = DateFormat.getDateInstance(DateFormat.SHORT, Locale.US);
        return dateFormat.format(date);
    }
}
Decrescendo answered 16/1, 2017 at 0:41 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.