blob: f438d2c79a59d81db7de5166b4de5fde10e805eb [file] [log] [blame]
/*
* ====================================================================
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.
* ====================================================================
*
* This software consists of voluntary contributions made by many
* individuals on behalf of the Apache Software Foundation. For more
* information on the Apache Software Foundation, please see
* <http://www.apache.org/>.
*
*/
package org.apache.hc.core5.util;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Properties;
/**
* Provides access to version information for HTTP components.
* Static methods are used to extract version information from property
* files that are automatically packaged with HTTP component release JARs.
* <p>
* All available version information is provided in strings, where
* the string format is informal and subject to change without notice.
* Version information is provided for debugging output and interpretation
* by humans, not for automated processing in applications.
* </p>
*
* @since 4.0
*/
public class VersionInfo {
/** A string constant for unavailable information. */
public final static String UNAVAILABLE = "UNAVAILABLE";
/** The filename of the version information files. */
public final static String VERSION_PROPERTY_FILE = "version.properties";
// the property names
public final static String PROPERTY_MODULE = "info.module";
public final static String PROPERTY_RELEASE = "info.release";
/**
* @deprecated This will be removed in 6.0.
*/
@Deprecated
public final static String PROPERTY_TIMESTAMP = "info.timestamp";
/** The package that contains the version information. */
private final String infoPackage;
/** The module from the version info. */
private final String infoModule;
/** The release from the version info. */
private final String infoRelease;
/** The timestamp from the version info. */
private final String infoTimestamp;
/** The classloader from which the version info was obtained. */
private final String infoClassloader;
/**
* An empty immutable {@code VersionInfo} array.
*/
private static final VersionInfo[] EMPTY_VERSION_INFO_ARRAY = new VersionInfo[0];
/**
* Instantiates version information.
*
* @param pckg the package
* @param module the module, or {@code null}
* @param release the release, or {@code null}
* @param time the build time, or {@code null}
* @param clsldr the class loader, or {@code null}
*/
protected VersionInfo(final String pckg, final String module,
final String release, final String time, final String clsldr) {
Args.notNull(pckg, "Package identifier");
infoPackage = pckg;
infoModule = (module != null) ? module : UNAVAILABLE;
infoRelease = (release != null) ? release : UNAVAILABLE;
infoTimestamp = (time != null) ? time : UNAVAILABLE;
infoClassloader = (clsldr != null) ? clsldr : UNAVAILABLE;
}
/**
* Obtains the package name.
* The package name identifies the module or informal unit.
*
* @return the package name, never {@code null}
*/
public final String getPackage() {
return infoPackage;
}
/**
* Obtains the name of the versioned module or informal unit.
* This data is read from the version information for the package.
*
* @return the module name, never {@code null}
*/
public final String getModule() {
return infoModule;
}
/**
* Obtains the release of the versioned module or informal unit.
* This data is read from the version information for the package.
*
* @return the release version, never {@code null}
*/
public final String getRelease() {
return infoRelease;
}
/**
* Obtains the timestamp of the versioned module or informal unit.
* This data is read from the version information for the package.
*
* @return the timestamp, never {@code null}
* @deprecated This will be removed in 6.0.
*/
@Deprecated
public final String getTimestamp() {
return infoTimestamp;
}
/**
* Obtains the classloader used to read the version information.
* This is just the {@code toString} output of the classloader,
* since the version information should not keep a reference to
* the classloader itself. That could prevent garbage collection.
*
* @return the classloader description, never {@code null}
*/
public final String getClassloader() {
return infoClassloader;
}
/**
* Provides the version information in human-readable format.
*
* @return a string holding this version information
*/
@Override
public String toString() {
final StringBuilder sb = new StringBuilder
(20 + infoPackage.length() + infoModule.length() +
infoRelease.length() + infoTimestamp.length() +
infoClassloader.length());
sb.append("VersionInfo(")
.append(infoPackage).append(':').append(infoModule);
// If version info is missing, a single "UNAVAILABLE" for the module
// is sufficient. Everything else just clutters the output.
if (!UNAVAILABLE.equals(infoRelease)) {
sb.append(':').append(infoRelease);
}
sb.append(')');
if (!UNAVAILABLE.equals(infoClassloader)) {
sb.append('@').append(infoClassloader);
}
return sb.toString();
}
/**
* Loads version information for a list of packages.
*
* @param pckgs the packages for which to load version info
* @param clsldr the classloader to load from, or
* {@code null} for the thread context classloader
*
* @return the version information for all packages found,
* never {@code null}
*/
public static VersionInfo[] loadVersionInfo(final String[] pckgs,
final ClassLoader clsldr) {
Args.notNull(pckgs, "Package identifier array");
final List<VersionInfo> vil = new ArrayList<>(pckgs.length);
for (final String pckg : pckgs) {
final VersionInfo vi = loadVersionInfo(pckg, clsldr);
if (vi != null) {
vil.add(vi);
}
}
return vil.toArray(EMPTY_VERSION_INFO_ARRAY);
}
/**
* Loads version information for a package.
*
* @param pckg the package for which to load version information,
* for example "org.apache.http".
* The package name should NOT end with a dot.
* @param clsldr the classloader to load from, or
* {@code null} for the thread context classloader
*
* @return the version information for the argument package, or
* {@code null} if not available
*/
public static VersionInfo loadVersionInfo(final String pckg, final ClassLoader clsldr) {
Args.notNull(pckg, "Package identifier");
final ClassLoader cl = clsldr != null ? clsldr : Thread.currentThread().getContextClassLoader();
Properties vip = null; // version info properties, if available
try {
// org.apache.http becomes
// org/apache/http/version.properties
try (final InputStream is = cl.getResourceAsStream(pckg.replace('.', '/') + "/" + VERSION_PROPERTY_FILE)) {
if (is != null) {
final Properties props = new Properties();
props.load(is);
vip = props;
}
}
} catch (final IOException ex) {
// shamelessly munch this exception
}
VersionInfo result = null;
if (vip != null) {
result = fromMap(pckg, vip, cl);
}
return result;
}
/**
* Instantiates version information from properties.
*
* @param pckg the package for the version information
* @param info the map from string keys to string values,
* for example {@link java.util.Properties}
* @param clsldr the classloader, or {@code null}
*
* @return the version information
*/
protected static VersionInfo fromMap(final String pckg, final Map<?, ?> info,
final ClassLoader clsldr) {
Args.notNull(pckg, "Package identifier");
String module = null;
String release = null;
if (info != null) {
module = (String) info.get(PROPERTY_MODULE);
if ((module != null) && (module.length() < 1)) {
module = null;
}
release = (String) info.get(PROPERTY_RELEASE);
if ((release != null) && ((release.length() < 1) ||
(release.equals("${project.version}")))) {
release = null;
}
} // if info
String clsldrstr = null;
if (clsldr != null) {
clsldrstr = clsldr.toString();
}
return new VersionInfo(pckg, module, release, null, clsldrstr);
}
/**
* Gets software information as {@code "<name>/<release> (Java/<java.version>)"}. If release is
* {@link #UNAVAILABLE}, it will be omitted.
* <p>
* For example:
* <pre>"Apache-HttpClient/4.3 (Java/1.6.0_35)"</pre>
*
* @param name the component name, like "Apache-HttpClient".
* @param pkg
* the package for which to load version information, for example "org.apache.http". The package name
* should NOT end with a dot.
* @param cls
* the class' class loader to load from, or {@code null} for the thread context class loader
* @since 4.3
*/
public static String getSoftwareInfo(final String name, final String pkg, final Class<?> cls) {
// determine the release version from packaged version info
final VersionInfo vi = VersionInfo.loadVersionInfo(pkg, cls.getClassLoader());
final String release = (vi != null) ? vi.getRelease() : VersionInfo.UNAVAILABLE;
final String javaVersion = System.getProperty("java.version");
String nameAndRelease = name;
if (!UNAVAILABLE.equals(release)) {
nameAndRelease += "/" + release;
}
return String.format("%s (Java/%s)", nameAndRelease, javaVersion);
}
}