Java print time of last compilation
I'm looking to embed a piece of code that will print o开发者_如何转开发ut the time when the current class was last compiled. How can this be implemented in Java?
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;
}
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.
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.
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.
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.
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"));
}
Let your build procedure create a property file containing the information you need, and then read the properties as a resource in your code
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");
}
}
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;
}
Use @CompileTime and create a field
@CompileTime
private static long COMPILE_TIME; // This field contains compilation time in ms
精彩评论