at.pcgamingfreaks.Version.java Source code

Java tutorial

Introduction

Here is the source code for at.pcgamingfreaks.Version.java

Source

/*
 *   Copyright (C) 2016 GeorgH93
 *
 *   This program is free software: you can redistribute it and/or modify
 *   it under the terms of the GNU General Public License as published by
 *   the Free Software Foundation, either version 3 of the License, or
 *   (at your option) any later version.
 *
 *   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 General Public License for more details.
 *
 *   You should have received a copy of the GNU General Public License
 *   along with this program. If not, see <http://www.gnu.org/licenses/>.
 */

package at.pcgamingfreaks;

import org.apache.commons.lang3.Validate;
import org.jetbrains.annotations.NotNull;

import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

public class Version {
    public static final String VERSION_STING_FORMAT = "[vV]?\\d+(\\.\\d+)*(-[^-\\s]+)*";
    private static final int SAME = 0, OLDER = -1, NEWER = 1;
    private static final String PRE_RELEASE_TAG_FORMAT = "\\w+\\d";
    private static final String[] PRE_RELEASE_TAGS = new String[] { "alpha", "beta", "pre", "rc", "snapshot" };
    private static final Map<String, Integer> PRE_RELEASE_TAG_VALUE_RESOLUTION = new ConcurrentHashMap<>();

    static {
        for (int i = 0; i < PRE_RELEASE_TAGS.length; i++) {
            PRE_RELEASE_TAG_VALUE_RESOLUTION.put(PRE_RELEASE_TAGS[i], (PRE_RELEASE_TAGS.length + 1 - i) * 10);
        }
    }

    private final String rawVersion;
    @SuppressWarnings("FieldCanBeLocal")
    private final String[] optionalTags;
    private final int[] version;
    private final int hashCode;

    /**
     * @param version A string representing this version. Must be in the format: {@value #VERSION_STING_FORMAT}
     * @throws InvalidVersionStringException The exception is thrown when the given string doesn't match the required format.
     */
    public Version(@NotNull String version) throws InvalidVersionStringException {
        this(version, false);
    }

    /**
     * @param version A string representing this version. Must be in the format: {@value #VERSION_STING_FORMAT}
     * @param ignoreOptionalTags Ignores tags like -alpha for the version comparison.
     * @throws InvalidVersionStringException The exception is thrown when the given string doesn't match the required format.
     */
    public Version(@NotNull String version, boolean ignoreOptionalTags) throws InvalidVersionStringException {
        //noinspection ConstantConditions
        if (version == null || version.isEmpty() || !isValidVersionString(version))
            throw new InvalidVersionStringException(
                    "The version string must be in the format: " + VERSION_STING_FORMAT);
        if (version.startsWith("v") || version.startsWith("V"))
            version = version.substring(1);
        this.rawVersion = version;
        // Prepare data
        String[] comps = version.split("-", 2);
        version = comps[0].replaceAll("(\\.0)*$", "");
        String options = comps.length > 1 ? comps[1] : "";
        this.optionalTags = options.split("-");
        comps = version.split("\\.");
        List<String> tags = (!ignoreOptionalTags) ? getAll(this.optionalTags, PRE_RELEASE_TAGS) : null;
        boolean notAFinalVersion = !ignoreOptionalTags && tags.size() > 0;
        this.version = new int[notAFinalVersion ? comps.length + 1 : comps.length];
        for (int i = 0; i < comps.length; i++) {
            this.version[i] = Integer.parseInt(comps[i]);
        }
        if (notAFinalVersion) {
            int last = 0;
            for (String str : tags) {
                if (last == 0)
                    last = Integer.MAX_VALUE;
                int preReleaseTagNumber = 0;
                String tag = str.toLowerCase();
                if (str.matches(PRE_RELEASE_TAG_FORMAT)) {
                    preReleaseTagNumber = Integer.parseInt(tag.substring(tag.length() - 1));
                    tag = tag.substring(0, tag.length() - 1);
                }
                last = (last - PRE_RELEASE_TAG_VALUE_RESOLUTION.get(tag)) + preReleaseTagNumber;
            }
            this.version[this.version.length - 1] = last;
            if (last > 0) {
                for (int i = this.version.length - 2; i >= 0; i--) {
                    if (this.version[i] > 0 || i == 0) {
                        this.version[i]--;
                        break;
                    }
                }
            }
        }
        this.hashCode = Arrays.hashCode(this.version);
    }

    private static List<String> getAll(String[] source, String[] searchForArray) {
        List<String> result = new LinkedList<>();
        for (String searchFor : searchForArray) {
            result.addAll(StringUtils.getAllContainingIgnoreCase(source, searchFor));
        }
        return result;
    }

    /**
     * Checks if the given version string matches the required format.
     *
     * @param version The String to check.
     * @return True if the string matches the format. False if not.
     */
    public static boolean isValidVersionString(String version) {
        return version.matches(VERSION_STING_FORMAT);
    }

    /**
     * Compares two version with each other.
     *
     * @param otherVersion The version to compare with.
     * @return -1 this older than otherVersion, 0 equals, 1 this newer than otherVersion
     */
    private byte compare(@NotNull Version otherVersion) {
        Validate.notNull(otherVersion);
        int c = Math.min(this.version.length, otherVersion.version.length);
        for (int i = 0; i < c; i++) {
            if (otherVersion.version[i] > this.version[i]) {
                return OLDER;
            } else if (otherVersion.version[i] < this.version[i]) {
                return NEWER;
            }
        }
        // If both version are the same for the length of the shorter version the version that has more digits probably is the newer one.
        if (this.version.length == otherVersion.version.length)
            return 0;
        boolean otherLonger = otherVersion.version.length > this.version.length;
        int[] longer = (otherLonger) ? otherVersion.version : this.version;
        byte result = SAME;
        for (int i = c; i < longer.length; i++) {
            if (longer[i] > 0) {
                result = (byte) ((otherLonger) ? OLDER : NEWER);
            }
        }
        return result;
    }

    //region comparision functions
    /**
     * Checks if the version is newer than the given version.
     *
     * @param otherVersion The version to compare with.
     * @return True if the version is newer, false if not.
     */
    public boolean newerThan(@NotNull Version otherVersion) {
        return compare(otherVersion) == NEWER;
    }

    /**
     * Checks if the version is newer or the same than the given version.
     *
     * @param otherVersion The version to compare with.
     * @return True if the version is newer or the same, false if not.
     */
    public boolean newerOrEqualThan(@NotNull Version otherVersion) {
        return compare(otherVersion) >= SAME;
    }

    /**
     * Checks if the version is older than the given version.
     *
     * @param otherVersion The version to compare with.
     * @return True if the version is older, false if not.
     */
    public boolean olderThan(@NotNull Version otherVersion) {
        return compare(otherVersion) == OLDER;
    }

    /**
     * Checks if the version is older or the same than the given version.
     *
     * @param otherVersion The version to compare with.
     * @return True if the version is older or the same, false if not.
     */
    public boolean olderOrEqualThan(@NotNull Version otherVersion) {
        return compare(otherVersion) <= SAME;
    }
    //endregion

    //region Overriding functions
    /**
     * Returns the original version string. But without the optional "v" at the start.
     *
     * @return The string representing this version.
     */
    @Override
    public String toString() {
        return this.rawVersion;
    }

    /**
     * Compares two versions if they are the same.
     *
     * @param otherVersion The version to compare with.
     * @return True if both versions are equal, false if not.
     */
    @Override
    public boolean equals(Object otherVersion) {
        return otherVersion instanceof Version && compare((Version) otherVersion) == SAME;
    }

    @Override
    public int hashCode() {
        return hashCode;
    }
    //endregion

    /**
     * This exception is thrown when the string representing an version is invalid.
     */
    public static class InvalidVersionStringException extends IllegalArgumentException {
        public InvalidVersionStringException(String string) {
            super(string);
        }
    }
}