VersionNumber.java

  1. /*******************************************************************************
  2.  * Copyright 2012 André Rouél
  3.  *
  4.  * Licensed under the Apache License, Version 2.0 (the "License");
  5.  * you may not use this file except in compliance with the License.
  6.  * You may obtain a copy of the License at
  7.  *
  8.  *   http://www.apache.org/licenses/LICENSE-2.0
  9.  *
  10.  * Unless required by applicable law or agreed to in writing, software
  11.  * distributed under the License is distributed on an "AS IS" BASIS,
  12.  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13.  * See the License for the specific language governing permissions and
  14.  * limitations under the License.
  15.  ******************************************************************************/
  16. package net.sf.uadetector;

  17. import java.io.Serializable;
  18. import java.util.ArrayList;
  19. import java.util.Arrays;
  20. import java.util.Collections;
  21. import java.util.List;
  22. import java.util.regex.Pattern;

  23. import javax.annotation.Nonnull;
  24. import javax.annotation.Nullable;

  25. import net.sf.qualitycheck.Check;
  26. import net.sf.qualitycheck.exception.IllegalStateOfArgumentException;
  27. import net.sf.uadetector.internal.util.AlphanumComparator;

  28. /**
  29.  * The {@code VersionNumber} class represents the version number of an operating system or User-Agent.<br>
  30.  * <br>
  31.  * A {@code VersionNumber} object is immutable, their values cannot be changed after creation.
  32.  *
  33.  * @author André Rouél
  34.  */
  35. public final class VersionNumber implements ReadableVersionNumber, Serializable {

  36.     /**
  37.      * Empty extension or addition of a version number
  38.      */
  39.     public static final String EMPTY_EXTENSION = "";

  40.     /**
  41.      * Empty group or category of a version number
  42.      */
  43.     public static final String EMPTY_GROUP = "";

  44.     /**
  45.      * Minimum number of numeric group a version number
  46.      */
  47.     private static final int MIN_GROUP_SIZE = 3;

  48.     /**
  49.      * Regular expression to find only numerical values ​​in strings
  50.      */
  51.     private static final Pattern NUMERIC = Pattern.compile("\\d+");

  52.     /**
  53.      * Separator between numeric groups of a version number
  54.      */
  55.     private static final char SEPARATOR = '.';

  56.     /**
  57.      * Serialization version
  58.      */
  59.     private static final long serialVersionUID = 1L;

  60.     /**
  61.      * Defines an empty or not set version number
  62.      */
  63.     public static final VersionNumber UNKNOWN = new VersionNumber(EMPTY_GROUP);

  64.     /**
  65.      * Checks a string that only numerical values ​​are present. Negative numbers are not included.
  66.      *
  67.      * @param text
  68.      *            string to be tested
  69.      * @return {@code true} if only numeric characters are present, otherwise {@code false}
  70.      */
  71.     private static boolean isNumeric(final String text) {
  72.         return NUMERIC.matcher(text).matches();
  73.     }

  74.     /**
  75.      * Interprets a string with version information. The last version number in the string will be searched and
  76.      * processed.
  77.      *
  78.      * @param text
  79.      *            string with version information
  80.      * @return an object of {@code VersionNumber}, never {@code null}
  81.      */
  82.     public static VersionNumber parseLastVersionNumber(@Nonnull final String text) {
  83.         return VersionParser.parseLastVersionNumber(Check.notNull(text, "text"));
  84.     }

  85.     /**
  86.      * Try to determine the version number of the operating system by parsing the user agent string.
  87.      *
  88.      *
  89.      * @param family
  90.      *            family of the operating system
  91.      * @param userAgent
  92.      *            user agent string
  93.      * @return extracted version number
  94.      */
  95.     public static VersionNumber parseOperatingSystemVersion(@Nonnull final OperatingSystemFamily family, @Nonnull final String userAgent) {
  96.         Check.notNull(family, "family");
  97.         Check.notNull(userAgent, "userAgent");
  98.         return VersionParser.parseOperatingSystemVersion(family, userAgent);
  99.     }

  100.     /**
  101.      * Interprets a string with version information. The first found group will be taken and processed.
  102.      *
  103.      * @param version
  104.      *            version as string
  105.      * @return an object of {@code VersionNumber}, never {@code null}
  106.      */
  107.     public static VersionNumber parseVersion(@Nonnull final String version) {
  108.         return VersionParser.parseVersion(Check.notNull(version, "version"));
  109.     }

  110.     /**
  111.      * Replaces all {@code null} values in the given list of groups with {@code VersionNumber#EMPTY_GROUP}.
  112.      *
  113.      * @param groups
  114.      *            list of numbers of a version number
  115.      * @return a new list of groups without {@code null} values
  116.      */
  117.     public static List<String> replaceNullValueWithEmptyGroup(@Nonnull final List<String> groups) {
  118.         Check.notNull(groups, "groups");

  119.         final List<String> result = new ArrayList<String>(groups.size());
  120.         for (final String group : groups) {
  121.             if (group == null) {
  122.                 result.add(EMPTY_GROUP);
  123.             } else {
  124.                 result.add(group);
  125.             }
  126.         }
  127.         for (int i = result.size(); i < MIN_GROUP_SIZE; i++) {
  128.             result.add(EMPTY_GROUP);
  129.         }
  130.         return result;
  131.     }

  132.     /**
  133.      * Converts the given list of numbers in a version string. The groups of the version number will be separated by a
  134.      * dot.
  135.      *
  136.      * @param groups
  137.      *            list of numbers of a version number
  138.      * @return a formated version string
  139.      */
  140.     private static String toVersionString(@Nonnull final List<String> groups) {
  141.         final StringBuilder builder = new StringBuilder(6);
  142.         int count = 0;
  143.         for (final String segment : groups) {
  144.             if (EMPTY_GROUP.equals(segment)) {
  145.                 break;
  146.             } else {
  147.                 if (count > 0) {
  148.                     builder.append(SEPARATOR);
  149.                 }
  150.                 builder.append(segment);
  151.             }
  152.             count++;
  153.         }
  154.         return builder.toString();
  155.     }

  156.     /**
  157.      * Extension or suffix of the version number consisting of alphanumeric and special characters
  158.      */
  159.     @Nonnull
  160.     private final String extension;

  161.     /**
  162.      * Groups, segments or categories of the version number
  163.      */
  164.     @Nonnull
  165.     private final List<String> groups;

  166.     /**
  167.      * Constructs a {@code VersionNumber} with the given numeric groups, such as major, minor and bugfix number.
  168.      *
  169.      * @param groups
  170.      *            list of numbers of a version number
  171.      * @throws net.sf.qualitycheck.exception.IllegalNullArgumentException
  172.      *             if the given argument is {@code null}
  173.      * @throws net.sf.qualitycheck.exception.IllegalStateOfArgumentException
  174.      *             if one of the segments of the version number is smaller than 0 and not empty
  175.      */
  176.     public VersionNumber(@Nonnull final List<String> groups) {
  177.         this(groups, EMPTY_EXTENSION);
  178.     }

  179.     /**
  180.      * Constructs a {@code VersionNumber} with the given numeric groups, such as major, minor and bugfix number and
  181.      * extension.
  182.      *
  183.      * @param groups
  184.      *            list of numbers of a version number
  185.      * @param extension
  186.      *            extension of a version number
  187.      * @throws net.sf.qualitycheck.exception.IllegalNullArgumentException
  188.      *             if one of the given arguments is {@code null}
  189.      * @throws net.sf.qualitycheck.exception.IllegalStateOfArgumentException
  190.      *             if one of the groups of the version number is not empty or a positive number
  191.      */
  192.     public VersionNumber(@Nonnull final List<String> groups, @Nonnull final String extension) {
  193.         Check.notNull(groups, "groups");
  194.         Check.notNull(extension, "extension");

  195.         final List<String> segments = replaceNullValueWithEmptyGroup(groups);
  196.         int i = 0;
  197.         for (final String segment : segments) {
  198.             if (!EMPTY_GROUP.equals(segment) && !isNumeric(segment)) {
  199.                 throw new IllegalStateOfArgumentException("The segment on position " + i + " (" + segment + ") must be a number.");
  200.             }
  201.             i++;
  202.         }

  203.         this.groups = segments;
  204.         this.extension = extension;
  205.     }

  206.     /**
  207.      * Constructs a {@code VersionNumber} with the given major number and without a minor and bugfix number.
  208.      *
  209.      * @param major
  210.      *            major group of the version number
  211.      * @throws net.sf.qualitycheck.exception.IllegalNullArgumentException
  212.      *             if the given argument is {@code null}
  213.      * @throws net.sf.qualitycheck.exception.IllegalStateOfArgumentException
  214.      *             if the major segment is smaller than 0 and not empty
  215.      */
  216.     public VersionNumber(@Nonnull final String major) {
  217.         this(Check.notNull(major, "major"), EMPTY_GROUP);
  218.     }

  219.     /**
  220.      * Constructs a {@code VersionNumber} with the given major, minor number and without a bugfix number.
  221.      *
  222.      * @param major
  223.      *            major group of the version number
  224.      * @param minor
  225.      *            minor group of the version number
  226.      * @throws net.sf.qualitycheck.exception.IllegalNullArgumentException
  227.      *             if one of the given arguments is {@code null}
  228.      * @throws net.sf.qualitycheck.exception.IllegalStateOfArgumentException
  229.      *             if the major or minor segment is smaller than 0 and not empty
  230.      */
  231.     public VersionNumber(@Nonnull final String major, @Nonnull final String minor) {
  232.         this(Check.notNull(major, "major"), Check.notNull(minor, "minor"), EMPTY_GROUP);
  233.     }

  234.     /**
  235.      * Constructs a {@code VersionNumber} with the given major, minor and bugfix number.
  236.      *
  237.      * @param major
  238.      *            major group of the version number
  239.      * @param minor
  240.      *            minor group of the version number
  241.      * @param bugfix
  242.      *            bugfix group of the version number
  243.      * @throws net.sf.qualitycheck.exception.IllegalNullArgumentException
  244.      *             if one of the given arguments is {@code null}
  245.      * @throws net.sf.qualitycheck.exception.IllegalStateOfArgumentException
  246.      *             if the major, minor or bugfix segment is smaller than 0 and not empty
  247.      */
  248.     public VersionNumber(@Nonnull final String major, @Nonnull final String minor, @Nonnull final String bugfix) {
  249.         this(Check.notNull(major, "major"), Check.notNull(minor, "minor"), Check.notNull(bugfix, "bugfix"), EMPTY_EXTENSION);
  250.     }

  251.     /**
  252.      * Constructs a {@code VersionNumber} with the given major, minor and bugfix number and extension.
  253.      *
  254.      * @param major
  255.      *            major group of the version number
  256.      * @param minor
  257.      *            minor group of the version number
  258.      * @param bugfix
  259.      *            bugfix group of the version number
  260.      * @param extension
  261.      *            extension of a version number
  262.      * @throws net.sf.qualitycheck.exception.IllegalNullArgumentException
  263.      *             if one of the given arguments is {@code null}
  264.      * @throws net.sf.qualitycheck.exception.IllegalStateOfArgumentException
  265.      *             if the major, minor or bugfix segment is smaller than 0 and not empty
  266.      */
  267.     public VersionNumber(@Nonnull final String major, @Nonnull final String minor, @Nonnull final String bugfix,
  268.             @Nonnull final String extension) {
  269.         this(Arrays.asList(Check.notNull(major, "major"), Check.notNull(minor, "minor"), Check.notNull(bugfix, "bugfix")), Check.notNull(
  270.                 extension, "extension"));
  271.     }

  272.     /**
  273.      * Compares this version number with the specified version number for order. Returns a negative integer, zero, or a
  274.      * positive integer as this version number is less than, equal to, or greater than the specified version number.
  275.      *
  276.      * @return a negative integer, zero, or a positive integer as this version number is less than, equal to, or greater
  277.      *         than the specified version number.
  278.      */
  279.     @Override
  280.     public int compareTo(@Nullable final ReadableVersionNumber other) {
  281.         int result = 0;
  282.         if (other == null) {
  283.             result = -1;
  284.         } else {
  285.             Check.notNull(other.getGroups(), "other.getGroups()");
  286.             final int length = groups.size() < other.getGroups().size() ? groups.size() : other.getGroups().size();
  287.             final AlphanumComparator comparator = new AlphanumComparator();
  288.             result = comparator.compare(toVersionString(groups.subList(0, length)), toVersionString(other.getGroups().subList(0, length)));
  289.             if (result == 0) {
  290.                 result = groups.size() > other.getGroups().size() ? 1 : groups.size() < other.getGroups().size() ? -1 : 0;
  291.             }
  292.             if (result == 0) {
  293.                 result = extension.compareTo(other.getExtension());
  294.             }
  295.             if (result == 0) {
  296.                 result = comparator.compare(toVersionString(), other.toVersionString());
  297.             }
  298.         }
  299.         return result;
  300.     }

  301.     /**
  302.      * Indicates whether some other object is "equal to" this version number.
  303.      *
  304.      * @return {@code true} if the given version number is equal to this one
  305.      */
  306.     @Override
  307.     public boolean equals(final Object obj) {
  308.         if (this == obj) {
  309.             return true;
  310.         }
  311.         if (obj == null) {
  312.             return false;
  313.         }
  314.         if (getClass() != obj.getClass()) {
  315.             return false;
  316.         }
  317.         final VersionNumber other = (VersionNumber) obj;
  318.         if (!groups.equals(other.groups)) {
  319.             return false;
  320.         }
  321.         if (!extension.equals(other.extension)) {
  322.             return false;
  323.         }
  324.         return true;
  325.     }

  326.     /**
  327.      * Gets the bugfix category of the version number.
  328.      */
  329.     @Override
  330.     public String getBugfix() {
  331.         return groups.get(2);
  332.     }

  333.     /**
  334.      * Gets the addition or extension of the version number.
  335.      *
  336.      * @return extension of the version number
  337.      */
  338.     @Override
  339.     public String getExtension() {
  340.         return extension;
  341.     }

  342.     /**
  343.      * Get all groups (or categories) of this version number. The first element in the list is the major category,
  344.      * followed by the minor and bugfix segment of the version number.<br>
  345.      * <br>
  346.      * The returned list of the version number segments is immutable.
  347.      *
  348.      * @return an unmodifiable view of the of the version number groups
  349.      */
  350.     @Override
  351.     public List<String> getGroups() {
  352.         return Collections.unmodifiableList(groups);
  353.     }

  354.     /**
  355.      * Gets the major category of the version number.
  356.      */
  357.     @Override
  358.     public String getMajor() {
  359.         return groups.get(0);
  360.     }

  361.     /**
  362.      * Gets the major category of the version number.
  363.      */
  364.     @Override
  365.     public String getMinor() {
  366.         return groups.get(1);
  367.     }

  368.     @Override
  369.     public int hashCode() {
  370.         final int prime = 31;
  371.         int result = 1;
  372.         result = prime * result + groups.hashCode();
  373.         result = prime * result + extension.hashCode();
  374.         return result;
  375.     }

  376.     /**
  377.      * Returns a string representation of the version number.
  378.      *
  379.      * @return a string representation of this version number
  380.      */
  381.     @Nonnull
  382.     @Override
  383.     public String toString() {
  384.         return "VersionNumber [groups=" + groups + ", extension=" + extension + "]";
  385.     }

  386.     /**
  387.      * Gets this version number as string.
  388.      *
  389.      * @return numeric groups as dot separated version number string
  390.      */
  391.     @Nonnull
  392.     @Override
  393.     public String toVersionString() {
  394.         return toVersionString(groups) + extension;
  395.     }

  396. }