26

I'm looking to embed a piece of code that will print out the time when the current class was last compiled. How can this be implemented in Java?

zer0stimulus
  • 22,306
  • 30
  • 110
  • 141

10 Answers10

15

This question has been answered a long time ago. But in case someone swings by here's a solution that works for me, similar to what Supah Fly suggested but supports jar and file.

private long classBuildTimeMillis() throws URISyntaxException, IllegalStateException, IllegalArgumentException {
    URL resource = getClass().getResource(getClass().getSimpleName() + ".class");
    if (resource == null) {
        throw new IllegalStateException("Failed to find class file for class: " + 
                                        getClass().getName());
    }

    if (resource.getProtocol().equals("file")) {

        return new File(resource.toURI()).lastModified();

    } else if (resource.getProtocol().equals("jar")) {

        String path = resource.getPath();
        return new File(path.substring(5, path.indexOf("!"))).lastModified();

    } else {

        throw new IllegalArgumentException("Unhandled url protocol: " + 
                resource.getProtocol() + " for class: " +
                getClass().getName() + " resource: " + resource.toString());
    }
}

But this won't handle zip files or a static context, and it throws exceptions instead of returning null if things go south. This is a bit more friendly:

private static final Date buildDate = getClassBuildTime();

/**
 * Handles files, jar entries, and deployed jar entries in a zip file (EAR).
 * @return The date if it can be determined, or null if not.
 */
private static Date getClassBuildTime() {
    Date d = null;
    Class<?> currentClass = new Object() {}.getClass().getEnclosingClass();
    URL resource = currentClass.getResource(currentClass.getSimpleName() + ".class");
    if (resource != null) {
        if (resource.getProtocol().equals("file")) {
            try {
                d = new Date(new File(resource.toURI()).lastModified());
            } catch (URISyntaxException ignored) { }
        } else if (resource.getProtocol().equals("jar")) {
            String path = resource.getPath();
            d = new Date( new File(path.substring(5, path.indexOf("!"))).lastModified() );    
        } else if (resource.getProtocol().equals("zip")) {
            String path = resource.getPath();
            File jarFileOnDisk = new File(path.substring(0, path.indexOf("!")));
            //long jfodLastModifiedLong = jarFileOnDisk.lastModified ();
            //Date jfodLasModifiedDate = new Date(jfodLastModifiedLong);
            try(JarFile jf = new JarFile (jarFileOnDisk)) {
                ZipEntry ze = jf.getEntry (path.substring(path.indexOf("!") + 2));//Skip the ! and the /
                long zeTimeLong = ze.getTime ();
                Date zeTimeDate = new Date(zeTimeLong);
                d = zeTimeDate;
            } catch (IOException|RuntimeException ignored) { }
        }
    }
    return d;
}
ggb667
  • 1,881
  • 2
  • 20
  • 44
krico
  • 5,723
  • 2
  • 25
  • 28
  • 3
    This old answer didn't handle zip files for something like a deployed war file on weblogic for instance, and it threw exceptions instead of returning null of the date can't be determined, and it didn't handle a static context. But I modified it to do that :). – ggb667 Mar 18 '14 at 19:46
  • Old answer but still comes up in google. Just note that getting the class like this will lead to unexpected results if you call that function from a subclass like a method defined in an abstract class. Instead of giving you the results for your current class, the subclass, you will get the results for the superclass. instead of `Class> currentClass = new Object() {}.getClass().getEnclosingClass();` it should be non-static and `Class> currentClass = this.getClass();` `if (currentClass.isAnonymousClass()) currentClass.getSuperclass();` – Kadser Apr 08 '20 at 14:35
14

There is no direct support for this in java, since there is no preprocessor. The closest equivalent is the "Build-Date" attribute in the JAR manifest. Many build systems add this attribute by default, or provide the means to add it.

You can then read the manifest of the JAR at runtime to get the date. The answer to this SO question describes how to read values from the JAR manifest.

The alternative is to use resource filtering to add the date into a properties file, which is then read at runtime. This is quite ad-hoc, non-standard and if you have many jars, with different compilation times, then this quickly becomes difficult to manage, unless you can factor this into a common part of how all the jars are built.

Community
  • 1
  • 1
mdma
  • 56,943
  • 12
  • 94
  • 128
  • 1
    +1. I was looking for a standard manifest property but didn't find it in good time. – Mark Peters Jul 26 '10 at 15:58
  • Note that embedding the build time in the manifest will cause the archive (JAR or WAR) to be rebuilt *every time* even if there are no source code changes. At least when using the Ant Jar or War tasks. – Marcus Junius Brutus Mar 07 '19 at 19:18
13

Since this was never mentioned, anyone looking to solve this problem by any means necessary, may find this as an appropriate, yet hacky, solution:

new Date(new File(getClass().getClassLoader().getResource(getClass().getCanonicalName().replace('.', '/') + ".class").toURI()).lastModified()))

It may not be pretty, and it very well may not be compatible on other platforms, but this is the only way I've found to figure out the compile date of the current class in native Java.

Yes Man
  • 411
  • 3
  • 15
  • 1
    Nice and simple solution for .class files, but will give an Exception when run from JAR though – golimar Nov 18 '19 at 13:36
5

It's a bit clunky, but you could do this with Ant filtering.

Pop the following method in your class:

public static String timeBuilt(){
    return "Built at @timeBuilt@ on @dateBuilt@";
}

Then put the following in your Ant build file.

<target name="get-time">
    <tstamp>
        <format property="buildTime" pattern="HH:mm:ss" locale="en,UK"/>
        <format property="buildDate" pattern="dd-MM-yyyy" locale="en,UK"/>
    </tstamp>
</target>

<target name="copy-files" depends="get-time">
    <filter token="timeBuilt" value="${buildTime}"/>
    <filter token="dateBuilt" value="${buildDate}"/>
    <copy includeemptydirs="false" todir="build" filtering="true">
        <fileset dir="src"/>
    </copy>
</target>

This will copy everything in the "src" directory to "build" and in doing so will replace @timeBuilt@ and @dateBuilt@ with the time and date of the build, respectively. Simply make your build target depend on copy-files and build from the "build" directory - not the "src" directory.

The advantage of replacing the content of a static method is that this will operate on a per-class basis - if you were to take the class files produced and combine them with some other class files that were built at another time, they would each know when they were built. Property files are sensible, but unless you were to have multiple property files, you would only be able to have a build time for the package as a whole.

Scott
  • 1,869
  • 3
  • 20
  • 25
4

Create a shell script that updates class code with the compilation time by replacing special placeholders:

public final class CompilationInfo
{ 
  public static final String TIME = "$COMPILE_TIME"; 
}

For more info, see this article.

trashgod
  • 203,806
  • 29
  • 246
  • 1,045
Michael Spector
  • 36,723
  • 6
  • 60
  • 88
  • 1
    We like making StackOverflow its own repository of information. Could you explain and given an example here, or at least summarize? – Mark Peters Jul 26 '10 at 15:43
  • Here's a link to the message which SO should accept: http://groups.google.com/group/comp.lang.java.help/msg/5177eeaea2ef9ad9?hl=en – Mark Peters Jul 26 '10 at 15:46
  • As suggested by @Mark Peters, you can use a direct link to the article. Please edit as desired. – trashgod Jul 26 '10 at 21:08
2

This is in my opinion the best solution. I'am using the export-function 'Runnable jar-file' of eclipse. This function generates the file "META-INF/MANIFEST.MF" which I'am using for determining the export-time. Those time tell's me when i had built the program. Under eclipse only the compile-time of the class of the parameter "obj" will be shown.

private static Date getDateOfJar(String path) throws IOException
{
    Date ret=null;
    JarFile jarFile = new JarFile(path);
    Enumeration ent = jarFile.entries();
    while (ent.hasMoreElements())
    {
        JarEntry entry = (JarEntry) ent.nextElement();

        String name = entry.getName();
        if (name.equals("META-INF/MANIFEST.MF"))
        {
            ret = new Date(entry.getTime());
            break;
        }
    }
    jarFile.close();

    return ret;
}

public static String getClassBuildTime(Object obj)
{
    String ret = "unknown";
    try
    {
        Class<?> currentClass = obj.getClass().getEnclosingClass();
        URL resource = currentClass.getResource(currentClass.getSimpleName() + ".class");
        if (resource != null)
        {
            if (resource.getProtocol().equals("file"))
            {
                try
                {
                    Date d = new Date(new File(resource.toURI()).lastModified());
                    ret = ""+d;
                }
                catch (URISyntaxException ignored)
                {
                }
            }
            else if (resource.getProtocol().equals("jar"))
            {
                String path = resource.getPath();
                Date d=getDateOfJar(path.substring(5, path.indexOf("!")));
                ret = ""+d;
            }
        }
    }
    catch (Exception e)
    {
        System.out.println("Error! FileLogger.getClassBuildTime() Exception e=" + e.getMessage());
        e.printStackTrace();
    }

    return ret;
}
Kokogino
  • 984
  • 1
  • 5
  • 17
kgw66
  • 21
  • 1
1

Not knowing a standard way to do this, my suggestion is similar to spektom's link but would be to add a property file to your jar that is populated by your build script (Ant has a built-in task for generating a property file). Maybe put it at /buildinfo.properties. Then create a Java class that simply polls that property file at runtime.

In Ant, it might look something like this:

...
<tstamp/>    
...
<propertyfile file="${output.dir}/buildinfo.properties">
   <entry key="build.date" value="${TSTAMP}"/>
</propertyfile>

And then corresponding Java

public Date getBuildDate() {
    Properties buildProps  = new Properties();
    buildProps.load(getClass().getResourceAsStream("/buildinfo.properties"));
    return someConversion(buildProps.getProperty("build.date"));
}
Mark Peters
  • 80,126
  • 17
  • 159
  • 190
  • There's no need to resort to an ad hoc property file. The MANIFEST file in a WAR archive is the normative place for this sort of information and it can be read by Java just as easily. However both approaches suffer from causing the archive to be generated anew **every time** even if there have been no changes in the underlying sources (assuming you're using the core Ant Jar or War tasks). – Marcus Junius Brutus Mar 07 '19 at 19:21
1

Let your build procedure create a property file containing the information you need, and then read the properties as a resource in your code

Thorbjørn Ravn Andersen
  • 73,784
  • 33
  • 194
  • 347
1

Here is my class for detecting the build time of your Java program. It uses the code from this answer as well.

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.JarFile;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;

public class BuildDate
{
    private static Date buildDate;

    static
    {
        try
        {
            buildDate = setBuildDate();
        } catch (Exception exception)
        {
            exception.printStackTrace();
        }
    }

    public static String getBuildDate()
    {
        int style = DateFormat.FULL;
        Locale locale = Locale.getDefault();
        DateFormat dateFormat = DateFormat.getDateInstance(style, locale);
        DateFormat timeFormat = DateFormat.getTimeInstance(style, locale);

        return dateFormat.format(buildDate) + " " + timeFormat.format(buildDate);
    }

    private static Date setBuildDate() throws Exception
    {
        if (ProgramDirectoryUtilities.runningFromIntelliJ())
        {
            return getClassBuildTime();
        } else
        {
            return getNewestFileDate();
        }
    }

    private static Date getNewestFileDate() throws Exception
    {
        String filePath = ProgramDirectoryUtilities.getJARFilePath();
        File file = new File(filePath);
        ZipFile zipFile = new ZipFile(file);
        Enumeration entries = zipFile.entries();

        long millis = -1;

        while (entries.hasMoreElements())
        {
            ZipEntry entry = (ZipEntry) entries.nextElement();

            if (!entry.isDirectory())
            {
                FileTime fileTime = entry.getLastModifiedTime();
                long currentMillis = fileTime.toMillis();

                if (millis < currentMillis)
                {
                    millis = currentMillis;
                }
            }
        }

        return new Date(millis);
    }

    /**
     * Handles files, jar entries, and deployed jar entries in a zip file (EAR).
     *
     * @return The date if it can be determined, or null if not.
     */
    private static Date getClassBuildTime() throws IOException, URISyntaxException
    {
        Date date = null;
        Class<?> currentClass = new Object()
        {
        }.getClass().getEnclosingClass();
        URL resource = currentClass.getResource(currentClass.getSimpleName() + ".class");
        if (resource != null)
        {
            switch (resource.getProtocol())
            {
                case "file":
                    date = new Date(new File(resource.toURI()).lastModified());
                    break;
                case "jar":
                {
                    String path = resource.getPath();
                    date = new Date(new File(path.substring(5, path.indexOf("!"))).lastModified());
                    break;
                }
                case "zip":
                {
                    String path = resource.getPath();
                    File jarFileOnDisk = new File(path.substring(0, path.indexOf("!")));
                    try (JarFile jarFile = new JarFile(jarFileOnDisk))
                    {
                        ZipEntry zipEntry = jarFile.getEntry(path.substring(path.indexOf("!") + 2));//Skip the ! and the /
                        long zeTimeLong = zipEntry.getTime();
                        date = new Date(zeTimeLong);
                    }
                    break;
                }
            }
        }
        return date;
    }
}

Utility class:

import java.io.File;
import java.lang.invoke.MethodHandles;
import java.net.URISyntaxException;
import java.net.URLDecoder;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;

public class ProgramDirectoryUtilities
{
    public static String getJARFilePath() throws URISyntaxException
    {
        return new File(MethodHandles.lookup().lookupClass().getProtectionDomain().getCodeSource().getLocation().toURI().getPath()).getAbsolutePath();
    }

    public static boolean runningFromJAR()
    {
        try
        {
            String jarFilePath = new File(MethodHandles.lookup().lookupClass().getProtectionDomain()
                    .getCodeSource()
                    .getLocation()
                    .getPath()).
                    toString();
            jarFilePath = URLDecoder.decode(jarFilePath, "UTF-8");

            try (ZipFile zipFile = new ZipFile(jarFilePath))
            {
                ZipEntry zipEntry = zipFile.getEntry("META-INF/MANIFEST.MF");

                return zipEntry != null;
            }
        } catch (Exception exception)
        {
            return false;
        }
    }

    public static String getProgramDirectory()
    {
        if (runningFromJAR())
        {
            return getCurrentJARDirectory();
        } else
        {
            return getCurrentProjectDirectory();
        }
    }

    private static String getCurrentProjectDirectory()
    {
        return new File("").getAbsolutePath();
    }

    private static String getCurrentJARDirectory()
    {
        try
        {
            return new File(MethodHandles.lookup().lookupClass().getProtectionDomain().getCodeSource().getLocation().toURI().getPath()).getParent();
        } catch (URISyntaxException exception)
        {
            exception.printStackTrace();
        }

        return null;
    }

    public static boolean runningFromIntelliJ()
    {
        String classPath = System.getProperty("java.class.path");
        return classPath.contains("idea_rt.jar");
    }
}
BullyWiiPlaza
  • 17,329
  • 10
  • 113
  • 185
1

Use @CompileTime and create a field

@CompileTime
private static long COMPILE_TIME; // This field contains compilation time in ms