/*!
* 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;
}
}