blob: 1c78694550bd5823f563165f4d8317eb2ec7d6f5 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF 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.apache.ignite.lang;
import java.io.Externalizable;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
import java.util.Date;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.ignite.IgniteCheckedException;
import org.apache.ignite.internal.IgniteVersionUtils;
import org.apache.ignite.internal.util.typedef.internal.U;
import org.jetbrains.annotations.NotNull;
/**
* Represents node version.
* <p>
* Node version can be acquired via {@link org.apache.ignite.cluster.ClusterNode#version()} method.
* <p>
* Two versions are compared in the following order: major number,
* minor number, maintenance number, revision timestamp.
*/
public class IgniteProductVersion implements Comparable<IgniteProductVersion>, Externalizable {
/** */
private static final long serialVersionUID = 0L;
/** Size of the {@link #revHash }*/
public static final int REV_HASH_SIZE = 20;
/** Size in bytes of serialized: 3 bytes (maj, min, maintenance version), 8 bytes - timestamp */
public static final int SIZE_IN_BYTES = 3 + 8 + REV_HASH_SIZE;
/** Regexp parse pattern. */
private static final Pattern VER_PATTERN =
Pattern.compile("(\\d+)\\.(\\d+)\\.(\\d+)([-.]([^0123456789][^-]+)(-SNAPSHOT)?)?(-(\\d+))?(-([\\da-f]+))?");
/** Major version number. */
private byte major;
/** Minor version number. */
private byte minor;
/** Maintenance version number. */
private byte maintenance;
/** Stage of development. */
private String stage;
/** Revision timestamp. */
private long revTs;
/** Revision hash. */
private byte[] revHash;
/**
* Empty constructor required by {@link Externalizable}.
*/
public IgniteProductVersion() {
// No-op.
}
/**
* @param major Major version number.
* @param minor Minor version number.
* @param maintenance Maintenance version number.
* @param revTs Revision timestamp.
* @param revHash Revision hash.
*/
public IgniteProductVersion(byte major, byte minor, byte maintenance, long revTs, byte[] revHash) {
this(major, minor, maintenance, "", revTs, revHash);
}
/**
* @param major Major version number.
* @param minor Minor version number.
* @param maintenance Maintenance version number.
* @param stage Stage of development.
* @param revTs Revision timestamp.
* @param revHash Revision hash.
*/
public IgniteProductVersion(byte major, byte minor, byte maintenance, String stage, long revTs, byte[] revHash) {
if (revHash != null && revHash.length != REV_HASH_SIZE) {
throw new IllegalArgumentException("Invalid length for SHA1 hash (must be "
+ REV_HASH_SIZE + "): " + revHash.length);
}
this.major = major;
this.minor = minor;
this.maintenance = maintenance;
this.stage = stage;
this.revTs = revTs;
this.revHash = revHash != null ? revHash : new byte[REV_HASH_SIZE];
}
/**
* Gets major version number.
*
* @return Major version number.
*/
public byte major() {
return major;
}
/**
* Gets minor version number.
*
* @return Minor version number.
*/
public byte minor() {
return minor;
}
/**
* Gets maintenance version number.
*
* @return Maintenance version number.
*/
public byte maintenance() {
return maintenance;
}
/**
* @return Stage of development.
*/
public String stage() {
return stage;
}
/**
* Gets revision timestamp.
*
* @return Revision timestamp.
*/
public long revisionTimestamp() {
return revTs;
}
/**
* Gets revision hash.
*
* @return Revision hash.
*/
public byte[] revisionHash() {
return revHash;
}
/**
* Gets release date.
*
* @return Release date.
*/
public Date releaseDate() {
return new Date(revTs * 1000);
}
/**
* @param major Major version number.
* @param minor Minor version number.
* @param maintenance Maintenance version number.
* @return {@code True} if this version is greater or equal than the one passed in.
*/
public boolean greaterThanEqual(int major, int minor, int maintenance) {
// NOTE: Unknown version is less than any other version.
if (major == this.major)
return minor == this.minor ? this.maintenance >= maintenance : this.minor > minor;
else
return this.major > major;
}
/** {@inheritDoc} */
@Override public int compareTo(@NotNull IgniteProductVersion o) {
// NOTE: Unknown version is less than any other version.
int res = Integer.compare(major, o.major);
if (res != 0)
return res;
res = Integer.compare(minor, o.minor);
if (res != 0)
return res;
res = Integer.compare(maintenance, o.maintenance);
if (res != 0)
return res;
return Long.compare(revTs, o.revTs);
}
/**
* @param o Other version.
* @return Compare result.
*/
public int compareToIgnoreTimestamp(@NotNull IgniteProductVersion o) {
int res = Integer.compare(major, o.major);
if (res != 0)
return res;
res = Integer.compare(minor, o.minor);
if (res != 0)
return res;
return Integer.compare(maintenance, o.maintenance);
}
/** {@inheritDoc} */
@Override public boolean equals(Object o) {
if (this == o)
return true;
if (!(o instanceof IgniteProductVersion))
return false;
IgniteProductVersion that = (IgniteProductVersion)o;
return revTs == that.revTs && maintenance == that.maintenance && minor == that.minor && major == that.major;
}
/** {@inheritDoc} */
@Override public int hashCode() {
int res = major;
res = 31 * res + minor;
res = 31 * res + maintenance;
res = 31 * res + (int)(revTs ^ (revTs >>> 32));
return res;
}
/** {@inheritDoc} */
@Override public void writeExternal(ObjectOutput out) throws IOException {
out.writeByte(major);
out.writeByte(minor);
out.writeByte(maintenance);
out.writeLong(revTs);
U.writeByteArray(out, revHash);
}
/** {@inheritDoc} */
@Override public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
major = in.readByte();
minor = in.readByte();
maintenance = in.readByte();
revTs = in.readLong();
revHash = U.readByteArray(in);
}
/** {@inheritDoc} */
@Override public String toString() {
String revTsStr = IgniteVersionUtils.formatBuildTimeStamp(revTs * 1000);
String hash = U.byteArray2HexString(revHash).toLowerCase();
hash = hash.length() > 8 ? hash.substring(0, 8) : hash;
return major + "." + minor + "." + maintenance + "#" + revTsStr + "-sha1:" + hash;
}
/**
* Tries to parse product version from it's string representation.
*
* @param verStr String representation of version.
* @return Product version.
*/
public static IgniteProductVersion fromString(String verStr) {
assert verStr != null;
if (verStr.endsWith("-DEV") || verStr.endsWith("-n/a")) // Development or built from source ZIP.
verStr = verStr.substring(0, verStr.length() - 4);
Matcher match = VER_PATTERN.matcher(verStr);
if (match.matches()) {
try {
byte major = Byte.parseByte(match.group(1));
byte minor = Byte.parseByte(match.group(2));
byte maintenance = Byte.parseByte(match.group(3));
String stage = "";
if (match.group(4) != null)
stage = match.group(4).substring(1);
long revTs = 0;
if (match.group(7) != null)
revTs = Long.parseLong(match.group(8));
byte[] revHash = null;
if (match.group(9) != null)
revHash = U.decodeHex(match.group(10).toCharArray());
return new IgniteProductVersion(major, minor, maintenance, stage, revTs, revHash);
}
catch (IllegalStateException | IndexOutOfBoundsException | NumberFormatException | IgniteCheckedException e) {
throw new IllegalStateException("Failed to parse version: " + verStr, e);
}
}
else
throw new IllegalStateException("Failed to parse version: " + verStr);
}
}