BroadwickVersion.java

/*
 * Copyright 2013 University of Glasgow.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package broadwick;

import java.io.IOException;
import java.net.InetAddress;
import java.net.URL;
import java.util.Enumeration;
import java.util.jar.Attributes;
import java.util.jar.Manifest;
import lombok.extern.slf4j.Slf4j;

/**
 * This is a simple class to obtain the version number of the project from the manifest file of the packaged jar.
 * To locate the version and build information of the framework, we locate the jar in which this object resides and
 * interrogate the Manifest.MF file of that jar for the build info. This approach depends very much on how the jar 
 * file is used, for example, if the project using the jar in which this framework is delivered uses one-jar (and the 
 * maven plug-in top use this) then the framework jar is contained neatly with the other dependent jars. Using the
 * maven assembly plug-in though expands the dependent jar files (and overwrites the manifest files and any other 
 * files with duplicate names it finds) so that at best the manifest file that is found will be the one of the project
 * using the framework and it is that build info that will be displayed and NOT the frameworks.
 */
@Slf4j
public final class BroadwickVersion {

    static {
        version = new BroadwickVersion();
    }

    /**
     * Private constructor to provide a singleton instance.
     */
    private BroadwickVersion() {
    }

    /**
     * Get the implementation number defined in the Manifest.MF of the jar. The build process get the label/tag of the
     * most recent commit to the SCM and adds it as the implementation number to the jars manifest.
     * @return the project version number that is obtained from the SCM.
     */
    public static String getImplementationVersion() {
        return BroadwickVersion.getImplementationVersion(getManifest());
    }

    /**
     * Get the build time in the Manifest.MF of the jar. The build process adds the time of the build to the jars
     * manifest.
     * @return the project build timestamp that is obtained from the SCM.
     */
    public static String getImplementationTimeStamp() {
        return BroadwickVersion.getImplementationTimeStamp(getManifest());
    }

    /**
     * Get the object corresponding to the Manifest.mf file.
     * @return the object for the Manifest file.
     */
    private static Manifest getManifest() {
        Manifest manifest = null;
        try {
            // First find the jar that contains this object.
            final String className = version.getClass().getSimpleName() + ".class";
            final String classPath = version.getClass().getResource(className).toString();
            final String manifestPath = classPath.substring(0, classPath.lastIndexOf('!') + 1) + "/META-INF/MANIFEST.MF";

            if (classPath.startsWith("jar")) {

                // Find the manifest path in the list of resources in the current jar file.
                final Enumeration<URL> allManifestFilesInJar = version.getClass().getClassLoader().getResources(java.util.jar.JarFile.MANIFEST_NAME);
                while (allManifestFilesInJar.hasMoreElements()) {
                    final URL element = allManifestFilesInJar.nextElement();

                    if (element.toString().equals(manifestPath)) {
                        manifest = new Manifest(element.openStream());
                        break;
                    }
                }
            }
        } catch (IOException ex) {
            log.error("Cannot read manifest file, {}", ex.getLocalizedMessage());
        }
        return manifest;
    }

    /**
     * Get the build timestamp from the supplied Manifest.mf file.
     * @param manifest the manifest file containing the timestamp info.
     * @return a string containing the project build timestamp.
     */
    private static String getImplementationTimeStamp(final Manifest manifest) {
        final StringBuilder sb = new StringBuilder();
        final Attributes attr = manifest.getMainAttributes();

        sb.append(attr.getValue(BUILD_TIMESTAMP));

        return sb.toString();
    }

    /**
     * Get the build version number from the supplied Manifest.mf file..
     * @param manifest the manifest file containing the version info.
     * @return a string containing the project version.
     */
    private static String getImplementationVersion(final Manifest manifest) {
        final StringBuilder sb = new StringBuilder();
        final Attributes attr = manifest.getMainAttributes();

        sb.append(attr.getValue(IMPL_VERSION));
        sb.append(String.format(BUILD_STRING_FORMAT, attr.getValue(IMPL_BUILD)));

        return sb.toString();
    }

    /**
     * Get the build version number and timestamp. This gets the following attributes in the manifest file
     * <code>Implementation-Version</code>
     * <code>Implementation-Build</code>
     * <code>Build-timestamp</code> If the manifest file is NOT the one for the framework (i.e. a model that utilises
     * the framework) then it will report the versions of both.
     * @return a string containing the version and timestamp.
     */
    public static String getVersionAndTimeStamp() {
        try {
            final Manifest manifest = getManifest();
            final StringBuilder sb = new StringBuilder();
            final Attributes attr = manifest.getMainAttributes();

            sb.append(String.format("Version %s ", attr.getValue(IMPL_VERSION)));
            sb.append(String.format("Build (%s - %s : %s) ", InetAddress.getLocalHost().getHostName(),
                                    attr.getValue(IMPL_BUILD), attr.getValue(BUILD_TIMESTAMP)));
            return sb.toString();
        } catch (Exception e) {
            return "UNKNOWN VERSION";
        }
    }

    private static BroadwickVersion version;
    private static final String BUILD_STRING_FORMAT = " : build %s ";
    private static final String IMPL_VERSION = "Implementation-Version";
    private static final String IMPL_BUILD = "Implementation-Build";
    private static final String BUILD_TIMESTAMP = "Build-timestamp";
}