package net.obnoxint.util; import java.io.Serializable; /** * <p> * Instances of this class represent a version number in the format of [Mayor version].[Minor version].[Revision].[Build].<br> * A markup (e.g -RC1) can be attached and will be ignored by the {@link #compareTo(VersionNumber)} method but not by the {@link #equals(Object)} method. * </p> * A textual representation (or return value of the toString() method) of this class could look like this: <i>1.0.10.500-RC1</i> */ public class VersionNumber implements Comparable<VersionNumber>, Serializable { /** * This is interface can be used in order to signalize that a class is utilizing a single static instance of the VersionNumber class. */ public static interface Versioned { /** * Gets the {@link VersionNumber} of a class implementing {@link Versioned}. * * @return the VersionNumber. */ public VersionNumber getVersionNumber(); } public static final VersionNumber NULL = new VersionNumber(0, 0, 0, 0, null); /** * The version segment separator. */ public static final String VERSION_SEGMENT_SEPARATOR = "."; /** * The markup segment separator. */ public static final String MARKUP_SEGMENT_SEPARATOR = "-"; private static final long serialVersionUID = 9142373700216765155L; /** * <p> * Creates an instance of VersionNumber based on a String. * </p> * <p> * The general contract of this method is<br> * <code> * VersionNumber vn = new VersionNumber(0, 0, 1, 0, null);<br> * boolean b = vn.equals(vn.fromString(vn.toString()));<br> * </code> where b is <i>true</i>. * <p> * * @param string the String representing a version number. * @return the VersionNumber or null if an instance of VersionNumber can not be build from the given String. */ public static VersionNumber fromString(final String string) { if (string != null) { int maj, min, rev, bld; String mu = ""; String[] split1, split2; split1 = string.split("\\" + VERSION_SEGMENT_SEPARATOR); if (split1.length == 4) { split2 = split1[3].split(MARKUP_SEGMENT_SEPARATOR); try { maj = Integer.parseInt(split1[0]); min = Integer.parseInt(split1[1]); rev = Integer.parseInt(split1[2]); bld = Integer.parseInt(split2[0]); if (split2.length > 1) { for (int i = 1; i < split2.length; i++) { mu += split2[i]; if (i < split2.length - 1) { mu += MARKUP_SEGMENT_SEPARATOR; } } } return new VersionNumber(maj, min, rev, bld, mu); } catch (final NumberFormatException e) {} } } return null; } /** * Tries to guess a VersionNumber based on a String. The returned value might be inaccurate. * * @param v the string. * @param sep the separator. * @return a new instance of VersionNumber or null if no guessing was possible. */ public static VersionNumber guessVersion(final String v, String sep) { if (v != null && !v.trim().isEmpty()) { if (sep == null || sep.isEmpty()) { sep = VERSION_SEGMENT_SEPARATOR; } final String[] s = v.split(sep); final int l = s.length; if (l > 0) { int maj = -1; int min = -1; int rev = -1; int bld = -1; String mu = ""; if (l == 1) { try { maj = Integer.parseInt(s[0]); return new VersionNumber(maj, min, rev, bld, mu); } catch (final NumberFormatException e) { return new VersionNumber(maj, min, rev, bld, s[0]); } } else if (l == 2) { try { maj = Integer.parseInt(s[0]); min = Integer.parseInt(s[1]); return new VersionNumber(maj, min, rev, bld, mu); } catch (final NumberFormatException e) { if (maj == -1) { return new VersionNumber(maj, min, rev, bld, s[0] + sep + s[1]); } if (min == -1) { return new VersionNumber(maj, min, rev, bld, s[1]); } return null; } } else if (l == 3) { try { maj = Integer.parseInt(s[0]); min = Integer.parseInt(s[1]); rev = Integer.parseInt(s[2]); return new VersionNumber(maj, min, rev, bld, mu); } catch (final NumberFormatException e) { if (maj == -1) { return new VersionNumber(maj, min, rev, bld, s[0] + sep + s[1] + sep + s[2]); } if (min == -1) { return new VersionNumber(maj, min, rev, bld, s[1] + sep + s[2]); } if (bld == -1) { return new VersionNumber(maj, min, rev, bld, s[2]); } return null; } } else if (l == 4) { try { maj = Integer.parseInt(s[0]); min = Integer.parseInt(s[1]); rev = Integer.parseInt(s[2]); bld = Integer.parseInt(s[3]); return new VersionNumber(maj, min, rev, bld, mu); } catch (final NumberFormatException e) { if (maj == -1) { return new VersionNumber(maj, min, rev, bld, s[0] + sep + s[1] + sep + s[2] + sep + s[3]); } if (min == -1) { return new VersionNumber(maj, min, rev, bld, s[1] + sep + s[2] + sep + s[3]); } if (bld == -1) { return new VersionNumber(maj, min, rev, bld, s[2] + sep + s[3]); } if (rev == -1) { return new VersionNumber(maj, min, rev, bld, s[3]); } return null; } } else if (l == 5) { try { maj = Integer.parseInt(s[0]); min = Integer.parseInt(s[1]); rev = Integer.parseInt(s[2]); bld = Integer.parseInt(s[3]); mu = s[4]; return new VersionNumber(maj, min, rev, bld, mu); } catch (final NumberFormatException e) { if (maj == -1) { return new VersionNumber(maj, min, rev, bld, s[0] + sep + s[1] + sep + s[2] + sep + s[3] + sep + s[4]); } if (min == -1) { return new VersionNumber(maj, min, rev, bld, s[1] + sep + s[2] + sep + s[3] + sep + s[4]); } if (bld == -1) { return new VersionNumber(maj, min, rev, bld, s[2] + sep + s[3] + sep + s[4]); } if (rev == -1) { return new VersionNumber(maj, min, rev, bld, s[3] + sep + s[4]); } return null; } } } } return null; } private final int major; private final int minor; private final int revision; private final int build; private final String markup; /** * <p> * Creates a new instance of VersionNumber. * </p> * <p> * Negative numbers are not allowed. If a negative number is given, it will be replaced by 0. * </p> * * @param major the major version. * @param minor the minor version. * @param revision the revision. * @param build the build number. * @param markup an optional markup or null. */ public VersionNumber(final int major, final int minor, final int revision, final int build, final String markup) { this.major = (major < 0) ? 0 : major; this.minor = (minor < 0) ? 0 : minor; this.revision = (revision < 0) ? 0 : revision; this.build = (build < 0) ? 0 : build; this.markup = (markup == null || markup.trim().isEmpty()) ? "" : markup.trim(); } /** * Creates a clone of an instance of VersionNumber. * * @param v the VersionNumber. */ public VersionNumber(final VersionNumber v) { this(v.major, v.minor, v.revision, v.build, v.markup); } @Override public int compareTo(VersionNumber o) { o = new VersionNumber(o.major, o.minor, o.revision, o.build, null); if (o.major == major && o.minor == minor && o.build == build && o.revision == revision) { return 0; } else { if (o.major > major) { return 1; } else if (o.major < major) { return -1; } else { if (o.minor > minor) { return 1; } else if (o.minor < minor) { return -1; } else { if (o.revision > revision) { return 1; } else if (o.revision < revision) { return -1; } else { if (o.build > build) { return 1; } else { return -1; } } } } } } @Override public boolean equals(final Object obj) { if (obj != null) { if (obj instanceof VersionNumber) { final VersionNumber o = (VersionNumber) obj; return o.major == major && o.minor == minor && o.revision == revision && o.build == build && o.markup == markup; } else if (obj instanceof String) { return equals(fromString((String) obj)); } } return false; } public boolean equalsIgnoreMarkup(final VersionNumber obj) { if (obj != null) { return obj.major == major && obj.minor == minor && obj.revision == revision && obj.build == build; } return false; } /** * @return the build number. */ public final int getBuild() { return build; } /** * @return the major version. */ public final int getMajor() { return major; } /** * @return the markup or an empty String if no markup has been defined. */ public final String getMarkup() { return markup; } /** * @return the minor version. */ public final int getMinor() { return minor; } /** * @return the revision. */ public final int getRevision() { return revision; } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + build; result = prime * result + major; result = prime * result + ((markup == null) ? 0 : markup.hashCode()); result = prime * result + minor; result = prime * result + revision; return result; } @Override public String toString() { final StringBuilder sb = new StringBuilder().append(major).append(VERSION_SEGMENT_SEPARATOR).append(minor).append(VERSION_SEGMENT_SEPARATOR).append(revision).append(VERSION_SEGMENT_SEPARATOR).append(build); if (!markup.isEmpty()) { sb.append(MARKUP_SEGMENT_SEPARATOR).append(markup); } return sb.toString(); } }