From 62061d8235d6079a26aa7c88fd66aca333ba1344 Mon Sep 17 00:00:00 2001 From: Andrew Phillips Date: Fri, 6 Jan 2012 22:56:57 -0500 Subject: [PATCH] [issue 802] Adding support for programmatically accessible version information --- core/pom.xml | 15 ++ .../main/java/org/jclouds/JcloudsVersion.java | 106 ++++++++++++++ .../META-INF/jclouds-version.properties | 1 + .../java/org/jclouds/JcloudsVersionTest.java | 133 ++++++++++++++++++ .../META-INF/jclouds-version.properties | 1 + 5 files changed, 256 insertions(+) create mode 100644 core/src/main/java/org/jclouds/JcloudsVersion.java create mode 100644 core/src/main/resources/META-INF/jclouds-version.properties create mode 100644 core/src/test/java/org/jclouds/JcloudsVersionTest.java create mode 100644 core/src/test/resources/META-INF/jclouds-version.properties diff --git a/core/pom.xml b/core/pom.xml index ebb06b6d8e..a7c5442c79 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -114,6 +114,21 @@ + + + ${project.basedir}/src/main/resources + + META-INF/jclouds-version.properties + + + + true + ${project.basedir}/src/main/resources + + META-INF/jclouds-version.properties + + + maven-jar-plugin diff --git a/core/src/main/java/org/jclouds/JcloudsVersion.java b/core/src/main/java/org/jclouds/JcloudsVersion.java new file mode 100644 index 0000000000..000133eb41 --- /dev/null +++ b/core/src/main/java/org/jclouds/JcloudsVersion.java @@ -0,0 +1,106 @@ +/** + * Licensed to jclouds, Inc. (jclouds) under one or more + * contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. jclouds 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. + */ +package org.jclouds; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; +import static java.lang.String.format; + +import java.io.IOException; +import java.util.Properties; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.jclouds.javax.annotation.Nullable; + +import com.google.common.annotations.VisibleForTesting; + +/** + * @author Andrew Phillips + */ +public class JcloudsVersion { + @VisibleForTesting + static final String VERSION_RESOURCE_FILE = "META-INF/jclouds-version.properties"; + private static final String VERSION_PROPERTY_NAME = "jclouds.version"; + + // TODO: stop supporting x.y.z-rc-n after the 1.3.0 release + // x.y.z or x.y.z-rc.n or x.y.z-rc-n, optionally with -SNAPSHOT suffix - see http://semver.org + private static final Pattern SEMANTIC_VERSION_PATTERN = + Pattern.compile("(\\d+)\\.(\\d+)\\.(\\d+)(?:-rc[-\\.](\\d+))?(?:-SNAPSHOT)?"); + + private static final JcloudsVersion INSTANCE = new JcloudsVersion(); + + public final int majorVersion; + public final int minorVersion; + public final int patchVersion; + public final boolean releaseCandidate; + private final String version; + + /** + * Non-null iff {@link #releaseCandidate} is {@code true} + */ + public final @Nullable Integer releaseCandidateVersion; + public final boolean snapshot; + + @VisibleForTesting + JcloudsVersion() { + this(readVersionPropertyFromClasspath()); + } + + private static String readVersionPropertyFromClasspath() { + Properties versionProperties = new Properties(); + try { + versionProperties.load(checkNotNull(Thread.currentThread().getContextClassLoader().getResourceAsStream(VERSION_RESOURCE_FILE), VERSION_RESOURCE_FILE)); + } catch (IOException exception) { + throw new IllegalStateException(format("Unable to load version resource file '%s'", VERSION_RESOURCE_FILE), exception); + } + return checkNotNull(versionProperties.getProperty(VERSION_PROPERTY_NAME), VERSION_PROPERTY_NAME); + } + + @VisibleForTesting + JcloudsVersion(String version) { + Matcher versionMatcher = SEMANTIC_VERSION_PATTERN.matcher(version); + checkArgument(versionMatcher.matches(), "Version '%s' did not match expected pattern '%s'", + version, SEMANTIC_VERSION_PATTERN); + this.version = version; + // a match will produce three or four matching groups (release candidate version optional) + majorVersion = Integer.valueOf(versionMatcher.group(1)); + minorVersion = Integer.valueOf(versionMatcher.group(2)); + patchVersion = Integer.valueOf(versionMatcher.group(3)); + String releaseCandidateVersionIfPresent = versionMatcher.group(4); + if (releaseCandidateVersionIfPresent != null) { + releaseCandidate = true; + releaseCandidateVersion = Integer.valueOf(releaseCandidateVersionIfPresent); + } else { + releaseCandidate = false; + releaseCandidateVersion = null; + } + // endsWith("T") would be cheaper but we only do this once... + snapshot = version.endsWith("-SNAPSHOT"); + } + + @Override + public String toString() { + return version; + } + + public static JcloudsVersion get() { + return INSTANCE; + } +} \ No newline at end of file diff --git a/core/src/main/resources/META-INF/jclouds-version.properties b/core/src/main/resources/META-INF/jclouds-version.properties new file mode 100644 index 0000000000..0169c2f63f --- /dev/null +++ b/core/src/main/resources/META-INF/jclouds-version.properties @@ -0,0 +1 @@ +jclouds.version=${project.version} \ No newline at end of file diff --git a/core/src/test/java/org/jclouds/JcloudsVersionTest.java b/core/src/test/java/org/jclouds/JcloudsVersionTest.java new file mode 100644 index 0000000000..14f4a23670 --- /dev/null +++ b/core/src/test/java/org/jclouds/JcloudsVersionTest.java @@ -0,0 +1,133 @@ +/** + * Licensed to jclouds, Inc. (jclouds) under one or more + * contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. jclouds 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. + */ +package org.jclouds; + +import static org.jclouds.JcloudsVersion.VERSION_RESOURCE_FILE; +import static org.testng.Assert.*; + +import java.io.InputStream; +import java.util.List; + +import org.testng.annotations.Test; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Iterables; + +/** + * @author Andrew Phillips + */ +@Test(singleThreaded = true) +public class JcloudsVersionTest { + + @Test + public void testFailsIfResourceFileMissing() { + ClassLoader original = Thread.currentThread().getContextClassLoader(); + Thread.currentThread().setContextClassLoader( + new ResourceHidingClassLoader(original, VERSION_RESOURCE_FILE)); + try { + new JcloudsVersion(); + fail("Expected NullPointerException"); + } catch (NullPointerException expected) { + } finally { + Thread.currentThread().setContextClassLoader(original); + } + } + + @Test(expectedExceptions = { IllegalArgumentException.class }) + public void testFailsIfInvalidVersion() { + new JcloudsVersion("${project.version}"); + } + + @Test + public void testExtractsMajorMinorPatchVersions() { + JcloudsVersion version = new JcloudsVersion("1.2.3"); + assertEquals(1, version.majorVersion); + assertEquals(2, version.minorVersion); + assertEquals(3, version.patchVersion); + } + + @Test + public void testSupportsNonSnapshot() { + JcloudsVersion version = new JcloudsVersion("1.2.3"); + assertFalse(version.snapshot, "Expected non-snapshot"); + } + + @Test + public void testRecognisesSnapshot() { + JcloudsVersion version = new JcloudsVersion("1.2.3-SNAPSHOT"); + assertTrue(version.snapshot, "Expected snapshot"); + } + + @Test + public void testSupportsNonReleaseCandidate() { + JcloudsVersion version = new JcloudsVersion("1.2.3"); + assertFalse(version.releaseCandidate, "Expected non-release candidate"); + assertNull(version.releaseCandidateVersion); + } + + @Test + public void testRecognisesReleaseCandidate() { + JcloudsVersion version = new JcloudsVersion("1.2.3-rc.4"); + assertTrue(version.releaseCandidate, "Expected release candidate"); + } + + // TODO: remove once x.y.z-rc-n support is dropped after 1.3.0 + @Test + public void testRecognisesNonSemverReleaseCandidate() { + JcloudsVersion version = new JcloudsVersion("1.2.3-rc-4"); + assertTrue(version.releaseCandidate, "Expected release candidate"); + } + + @Test + public void testExtractsReleaseCandidateVersion() { + JcloudsVersion version = new JcloudsVersion("1.2.3-rc.4"); + assertEquals(Integer.valueOf(4), version.releaseCandidateVersion); + } + + // TODO: remove once x.y.z-rc-n support is dropped after 1.3.0 + @Test + public void testExtractsNonSemverReleaseCandidateVersion() { + JcloudsVersion version = new JcloudsVersion("1.2.3-rc-4"); + assertEquals(Integer.valueOf(4), version.releaseCandidateVersion); + } + + @Test + public void testRecognisesReleaseCandidateSnapshot() { + JcloudsVersion version = new JcloudsVersion("1.2.3-rc-4-SNAPSHOT"); + assertTrue(version.releaseCandidate, "Expected release candidate"); + assertTrue(version.snapshot, "Expected snapshot"); + } + + private static class ResourceHidingClassLoader extends ClassLoader { + private final ClassLoader delegate; + private final List resourcesToHide; + + private ResourceHidingClassLoader(ClassLoader delegate, String... resourcesToHide) { + this.delegate = delegate; + this.resourcesToHide = ImmutableList.copyOf(resourcesToHide); + } + + @Override + public InputStream getResourceAsStream(String name) { + return (Iterables.contains(resourcesToHide, name) + ? null + : delegate.getResourceAsStream(name)); + } + } +} \ No newline at end of file diff --git a/core/src/test/resources/META-INF/jclouds-version.properties b/core/src/test/resources/META-INF/jclouds-version.properties new file mode 100644 index 0000000000..6e5bce894e --- /dev/null +++ b/core/src/test/resources/META-INF/jclouds-version.properties @@ -0,0 +1 @@ +jclouds.version=0.0.0-SNAPSHOT \ No newline at end of file