001/*
002 * The contents of this file are subject to the terms of the Common Development and
003 * Distribution License (the License). You may not use this file except in compliance with the
004 * License.
005 *
006 * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
007 * specific language governing permission and limitations under the License.
008 *
009 * When distributing Covered Software, include this CDDL Header Notice in each file and include
010 * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
011 * Header, with the fields enclosed by brackets [] replaced by your own identifying
012 * information: "Portions copyright [year] [name of copyright owner]".
013 *
014 * Copyright 2015 ForgeRock AS.
015 */
016
017package org.forgerock.http.routing;
018
019import java.util.Objects;
020import java.util.regex.Matcher;
021import java.util.regex.Pattern;
022
023/**
024 * Represents some version in the form majorNumber.minorNumber,
025 * for instance 2.4.
026 */
027public final class Version implements Comparable<Version> {
028
029    private static final Version[] DOT_ZERO_CACHE = new Version[10];
030    private static final Pattern REGEX = Pattern.compile("(\\d+)(\\.(\\d+))?");
031
032    static {
033        for (int i = 0; i < DOT_ZERO_CACHE.length; i++) {
034            DOT_ZERO_CACHE[i] = new Version(i, 0);
035        }
036    }
037
038    private final int major;
039    private final int minor;
040
041    /**
042     * Creates a new version using the provided version information.
043     *
044     * @param major
045     *         Major version number.
046     * @param minor
047     *         Minor version number.
048     *
049     * @return The version.
050     */
051    public static Version version(final int major, final int minor) {
052        if (minor == 0 && major >= 0 && major < DOT_ZERO_CACHE.length) {
053            return DOT_ZERO_CACHE[major];
054        }
055        return new Version(major, minor);
056    }
057
058    /**
059     * Creates a new version using the provided version information and a minor.
060     *
061     * @param major
062     *         Major version number.
063     *
064     * @return The version.
065     */
066    public static Version version(final int major) {
067        return version(major, 0);
068    }
069
070    private Version(final int major, final int minor) {
071        this.major = major;
072        this.minor = minor;
073    }
074
075    /**
076     * Parses the string argument as a version. The string must be one of the
077     * following forms:
078     * <pre>
079     * major
080     * major.minor
081     * </pre>
082     *
083     * @param s
084     *         The non-{@code null} string to be parsed as a version.
085     *
086     * @return The parsed version.
087     *
088     * @throws IllegalArgumentException
089     *         If the string does not contain a parsable version.
090     */
091    public static Version version(final String s) {
092        Matcher matcher = REGEX.matcher(s);
093        if (matcher.matches()) {
094            if (matcher.group(3) == null) {
095                return version(Integer.parseInt(matcher.group(1)));
096            } else {
097                return version(Integer.parseInt(matcher.group(1)), Integer.parseInt(matcher.group(3)));
098            }
099        } else {
100            throw new IllegalArgumentException("Invalid version string " + s);
101        }
102    }
103
104    /**
105     * Returns the major version number.
106     *
107     * @return The major version number.
108     */
109    public int getMajor() {
110        return major;
111    }
112
113    /**
114     * Returns the minor version number.
115     *
116     * @return The minor version number.
117     */
118    public int getMinor() {
119        return minor;
120    }
121
122    @Override
123    public boolean equals(Object o) {
124        if (this == o) {
125            return true;
126        }
127        if (o == null || getClass() != o.getClass()) {
128            return false;
129        }
130        Version version = (Version) o;
131        return Objects.equals(major, version.major)
132                && Objects.equals(minor, version.minor);
133    }
134
135    @Override
136    public int hashCode() {
137        return Objects.hash(major, minor);
138    }
139
140    @Override
141    public int compareTo(final Version that) {
142        if (major != that.major) {
143            return major - that.major;
144        }
145        if (minor != that.minor) {
146            return minor - that.minor;
147        }
148        return 0;
149    }
150
151    @Override
152    public String toString() {
153        return major + "." + minor;
154    }
155
156    /**
157     * Returns {@code false} if:
158     * <ul>
159     *     <li>
160     *         the MAJOR version numbers are not the same.
161     *     </li>
162     *     <li>
163     *         the MAJOR version numbers are the same but {@code this} MINOR
164     *         version number is LOWER than {@code that} MINOR version number.
165     *     </li>
166     * </ul>
167     *
168     * <p>i.e. this version number - "2.0", that version number - "2.1" WILL
169     * NOT match, but this version number - "2.4", that version number - "2.1"
170     * WILL match. In other words, verifies ascending compatibility.</p>
171     *
172     * @param that The {@code Version} to match against.
173     * @return {@code true} if both MAJOR version numbers are the same and if
174     * {@code this} MINOR version number is HIGHER than {@code that} MINOR
175     * version number.
176     */
177    public boolean isCompatibleWith(Version that) {
178        return that != null && this.major == that.major && this.minor >= that.minor;
179    }
180}