/*! * This program is free software; you can redistribute it and/or modify it under the * terms of the GNU Lesser General Public License, version 2.1 as published by the Free Software * Foundation. * * You should have received a copy of the GNU Lesser General Public License along with this * program; if not, you can obtain a copy at http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html * or from the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * See the GNU Lesser General Public License for more details. * * Copyright (c) 2002-2013 Pentaho Corporation.. All rights reserved. */ package org.pentaho.reporting.libraries.base.versioning; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.pentaho.reporting.libraries.base.util.ObjectUtilities; import org.pentaho.reporting.libraries.base.util.StringUtils; import java.io.BufferedInputStream; import java.io.InputStream; import java.net.URL; import java.util.Enumeration; import java.util.HashMap; import java.util.Map; import java.util.StringTokenizer; import java.util.jar.Attributes; import java.util.jar.Manifest; /** * A utility class for reading versioning information from a Manifest file. * * @author Thomas Morgner */ public class VersionHelper { private static class ManifestCache { private Map<String, Manifest> manifests; private Map<String, Manifest> manifestsByURL; public ManifestCache() { manifests = new HashMap<String, Manifest>(); manifestsByURL = new HashMap<String, Manifest>(); } public synchronized Manifest get( final String title ) { return manifests.get( title ); } public synchronized void set( final String title, final String url, final Manifest manifest ) { if ( title != null ) { manifests.put( title, manifest ); } manifestsByURL.put( url, manifest ); } public synchronized Manifest getByURL( final String url ) { return manifestsByURL.get( url ); } } private static final Log logger = LogFactory.getLog( VersionHelper.class ); private static final ManifestCache manifestCache = new ManifestCache(); public static final String SNAPSHOT_TOKEN = "SNAPSHOT"; private String version; private String title; private String productId; private String releaseMilestone; private String releaseMinor; private String releaseMajor; private String releaseNumber; private String releasePatch; private String releaseBuildNumber; private ProjectInformation projectInformation; /** * Loads the versioning information for the given project-information structure using the project information's * internal name as lookup key. * * @param projectInformation the project we load information for. */ public VersionHelper( final ProjectInformation projectInformation ) { if ( projectInformation == null ) { throw new NullPointerException(); } this.projectInformation = projectInformation; Manifest manifest = manifestCache.get( projectInformation.getInternalName() ); if ( manifest == null ) { final ClassLoader loader = projectInformation.getClass().getClassLoader(); try { final Enumeration resources = loader.getResources( "META-INF/MANIFEST.MF" ); while ( resources.hasMoreElements() ) { final URL url = (URL) resources.nextElement(); final String urlAsText = url.toURI().toString(); Manifest maybeManifest = manifestCache.getByURL( urlAsText ); if ( maybeManifest == null ) { final InputStream inputStream = url.openStream(); try { maybeManifest = new Manifest( new BufferedInputStream( inputStream ) ); } finally { inputStream.close(); } } final Attributes attr = getAttributes( maybeManifest, projectInformation.getInternalName() ); final String maybeTitle = getValue( attr, "Implementation-ProductID", null ); if ( maybeTitle != null ) { manifestCache.set( maybeTitle, urlAsText, maybeManifest ); if ( maybeTitle.equals( projectInformation.getInternalName() ) ) { manifest = maybeManifest; break; } } else { manifestCache.set( null, urlAsText, maybeManifest ); } } } catch ( Exception e ) { // Ignore; Maybe log. if ( logger.isDebugEnabled() ) { logger.debug( "Failed to read manifest for retrieving library version information for " + projectInformation.getProductId(), e ); } } } if ( manifest != null ) { init( manifest ); } else { if ( logger.isDebugEnabled() ) { logger.debug( "Failed to create version information for " + projectInformation.getInternalName() ); } version = "TRUNK.development"; title = projectInformation.getInternalName(); productId = projectInformation.getInternalName(); releaseMajor = "999"; releaseMinor = "999"; releaseMilestone = "999"; releasePatch = "0"; releaseBuildNumber = SNAPSHOT_TOKEN; releaseNumber = createReleaseVersion(); } } /** * Initializes the instance, reaading the properties from the input stream. * * @param props the manifest. * @return true, if the manifest contains version information about this library, false otherwise. */ private boolean init( final Manifest props ) { try { final Attributes attr = getAttributes( props, projectInformation.getInternalName() ); final String maybeTitle = getValue( attr, "Implementation-ProductID", null ); if ( ObjectUtilities.equal( projectInformation.getInternalName(), maybeTitle ) == false ) { return false; } title = getValue( attr, "Implementation-Title", maybeTitle ); version = getValue( attr, "Implementation-Version", "" ); parseVersion( version ); productId = maybeTitle; if ( productId.length() == 0 ) { productId = createProductId(); } return true; } catch ( final Exception e ) { return false; } } protected void parseVersion( String version ) { if ( version == null || version.length() == 0 ) { version = "TRUNK.development"; } releaseMajor = "999"; releaseMinor = "999"; releaseMilestone = "999"; releasePatch = "0"; releaseBuildNumber = SNAPSHOT_TOKEN; if ( version.startsWith( "TRUNK" ) == false ) { // format is something like 3.8.0[.x]-GA.12345 final int dashPos = version.indexOf( '-' ); final String versionNumber; final String implIndicator; if ( dashPos != -1 ) { versionNumber = version.substring( 0, dashPos ); implIndicator = version.substring( dashPos + 1 ); } else { versionNumber = version; implIndicator = ""; } if ( StringUtils.isEmpty( versionNumber ) == false ) { final StringTokenizer tokNum = new StringTokenizer( versionNumber, "." ); if ( tokNum.hasMoreTokens() ) { releaseMajor = tokNum.nextToken(); } if ( tokNum.hasMoreTokens() ) { releaseMinor = tokNum.nextToken(); } if ( tokNum.hasMoreTokens() ) { releaseMilestone = tokNum.nextToken(); } if ( tokNum.hasMoreTokens() ) { releasePatch = tokNum.nextToken(); } final StringTokenizer tokImpl = new StringTokenizer( implIndicator, "." ); if ( tokImpl.hasMoreTokens() ) { releaseBuildNumber = tokImpl.nextToken(); } } } releaseNumber = createReleaseVersion(); } /** * Looks up the attributes for the given module specified by <code>name</code> in the given Manifest. * * @param props the manifest where to search for the attributes. * @param name the name of the module. * @return the attributes for the module or the main attributes if the jar contains no such module. */ private Attributes getAttributes( final Manifest props, final String name ) { final Attributes attributes = props.getAttributes( name ); if ( attributes == null ) { return props.getMainAttributes(); } return attributes; } /** * Looks up a single value in the given attribute collection using the given key. If the key is not contained in the * attributes, this method returns the default value specified as parameter. * * @param attrs the attributes where to lookup the key. * @param name the name of the key to use for the lookup. * @param defaultValue the default value to return in case the attributes contain no such key. * @return the value from the attributes or the default values. */ private String getValue( final Attributes attrs, final String name, final String defaultValue ) { final String value = attrs.getValue( name ); if ( value == null ) { return defaultValue; } return value.trim(); } /** * Creates a product-id string, which is the implementation title plus the optional version information. * * @return the product id string. */ private String createProductId() { if ( version.trim().length() == 0 ) { return title; } return title + '-' + version; } /** * Creates a version string using the major, minor and milestone version information and the build number. * * @return the release version. */ private String createReleaseVersion() { final StringBuilder buffer = new StringBuilder( 50 ); buffer.append( releaseMajor ); buffer.append( '.' ); buffer.append( releaseMinor ); buffer.append( '.' ); buffer.append( releaseMilestone ); if ( releasePatch.length() > 0 ) { buffer.append( '-' ); buffer.append( releasePatch ); } if ( releaseBuildNumber.length() > 0 ) { buffer.append( " (Build " ); buffer.append( releaseBuildNumber ); buffer.append( ')' ); } return buffer.toString(); } /** * Returns the full version string as computed by createVersion(). * * @return the version string. */ public String getVersion() { return version; } /** * Returns the implementation title as specified in the manifest. * * @return the implementation title. */ public String getTitle() { return title; } /** * Returns the product id as computed by createProductId(). * * @return the product id. * @see #createProductId() */ public String getProductId() { return productId; } /** * Returns the release milestone number. Defaults to 999 if not given in the manifest. * * @return the milestone number. */ public String getReleaseMilestone() { return releaseMilestone; } /** * Returns the release minor number. Defaults to 999 if not given in the manifest. * * @return the minor version number. */ public String getReleaseMinor() { return releaseMinor; } /** * Returns the release major number. Defaults to 999 if not given in the manifest. * * @return the major version number. */ public String getReleaseMajor() { return releaseMajor; } /** * Returns the release candidate token. Defaults to 999 if not given in the manifest. * * @return the candidate token. * @deprecated No longer used. */ @Deprecated public String getReleaseCandidateToken() { return ""; } /** * Returns the release patch number. Defaults to zero if not given in the manifest. * * @return the patch version number. */ public String getReleasePatch() { return releasePatch; } /** * Returns the release number. * * @return the release number. */ public String getReleaseNumber() { return releaseNumber; } /** * Returns the release build number. * * @return the build-number). */ public String getReleaseBuildNumber() { return releaseBuildNumber; } }