Skip to content

Commit

Permalink
release 3.1.1
Browse files Browse the repository at this point in the history
time zone bug fixed
network interface test is stable
  • Loading branch information
verhas committed May 20, 2019
1 parent f44c6e8 commit 6d34ed2
Show file tree
Hide file tree
Showing 6 changed files with 86 additions and 73 deletions.
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
<modelVersion>4.0.0</modelVersion>
<groupId>com.javax0.license3j</groupId>
<artifactId>license3j</artifactId>
<version>3.1.0</version>
<version>3.1.1</version>
<packaging>jar</packaging>

<name>License3j</name>
Expand Down
6 changes: 6 additions & 0 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -324,6 +324,12 @@ The type is written in all capital letters as listed above `BINARY`,
value of the feature. The type along with the separating `:` can be
missing in case it is `STRING`.

When a `DATE` feature is converted to and from text then the actual
value should be interpreted as time zone independent value. (Note that
there was a bug in 3.X.X releases prior version 3.1.1 that used the
local time zone to interpret text representation of the date/time
values.)

The values are encoded as text in a human-readable and editable way.
When a value cannot fit on a single line, for example, a multi-line
string then the feature value starts with the characters `<<` and it is
Expand Down
115 changes: 61 additions & 54 deletions src/main/java/javax0/license3j/Feature.java
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import java.util.Arrays;
import java.util.Base64;
import java.util.Date;
import java.util.TimeZone;
import java.util.function.BiFunction;
import java.util.function.Function;

Expand Down Expand Up @@ -39,12 +40,12 @@
*/
public class Feature {
private static final String[] DATE_FORMAT =
{"yyyy-MM-dd HH:mm:ss.SSS",
"yyyy-MM-dd HH:mm:ss",
"yyyy-MM-dd HH:mm",
"yyyy-MM-dd HH",
"yyyy-MM-dd"
};
{"yyyy-MM-dd HH:mm:ss.SSS",
"yyyy-MM-dd HH:mm:ss",
"yyyy-MM-dd HH:mm",
"yyyy-MM-dd HH",
"yyyy-MM-dd"
};
private static final int VARIABLE_LENGTH = -1;
private final String name;
private final Type type;
Expand All @@ -56,14 +57,20 @@ private Feature(String name, Type type, byte[] value) {
this.value = value;
}

private static SimpleDateFormat getUTCDateFormat(String format){
final var simpleDateFormat = new SimpleDateFormat(format);
simpleDateFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
return simpleDateFormat;
}

private static String dateFormat(Object date) {
return new SimpleDateFormat(DATE_FORMAT[0]).format(date);
return getUTCDateFormat(DATE_FORMAT[0]).format(date);
}

private static Date dateParse(String date) {
for (var format : DATE_FORMAT) {
try {
return new SimpleDateFormat(format).parse(date);
return getUTCDateFormat(format).parse(date);
} catch (ParseException ignored) {
}
}
Expand Down Expand Up @@ -144,8 +151,8 @@ public byte[] serialized() {
final var nameLength = Integer.BYTES + nameBuffer.length;
final var valueLength = type.fixedSize == VARIABLE_LENGTH ? Integer.BYTES + value.length : type.fixedSize;
final var buffer = ByteBuffer.allocate(typeLength + nameLength + valueLength)
.putInt(type.serialized)
.putInt(nameBuffer.length);
.putInt(type.serialized)
.putInt(nameBuffer.length);
if (type.fixedSize == VARIABLE_LENGTH) {
buffer.putInt(value.length);
}
Expand Down Expand Up @@ -293,56 +300,56 @@ public Date getDate() {

private enum Type {
BINARY(1, VARIABLE_LENGTH,
Feature::getBinary,
(name, value) -> Create.binaryFeature(name, (byte[]) value),
ba -> Base64.getEncoder().encodeToString((byte[]) ba), enc -> Base64.getDecoder().decode(enc)),
Feature::getBinary,
(name, value) -> Create.binaryFeature(name, (byte[]) value),
ba -> Base64.getEncoder().encodeToString((byte[]) ba), enc -> Base64.getDecoder().decode(enc)),
STRING(2, VARIABLE_LENGTH,
Feature::getString,
(name, value) -> Create.stringFeature(name, (String) value),
Object::toString, s -> s),
Feature::getString,
(name, value) -> Create.stringFeature(name, (String) value),
Object::toString, s -> s),
BYTE(3, Byte.BYTES,
Feature::getByte,
(name, value) -> Create.byteFeature(name, (Byte) value),
b -> String.format("0x%02X", (byte) (Byte) b), NumericParser.Byte::parse),
Feature::getByte,
(name, value) -> Create.byteFeature(name, (Byte) value),
b -> String.format("0x%02X", (byte) (Byte) b), NumericParser.Byte::parse),
SHORT(4, Short.BYTES,
Feature::getShort,
(name, value) -> Create.shortFeature(name, (Short) value),
Object::toString, NumericParser.Short::parse),
Feature::getShort,
(name, value) -> Create.shortFeature(name, (Short) value),
Object::toString, NumericParser.Short::parse),
INT(5, Integer.BYTES,
Feature::getInt,
(name, value) -> Create.intFeature(name, (Integer) value),
Object::toString, NumericParser.Int::parse),
Feature::getInt,
(name, value) -> Create.intFeature(name, (Integer) value),
Object::toString, NumericParser.Int::parse),
LONG(6, Long.BYTES,
Feature::getLong,
(name, value) -> Create.longFeature(name, (Long) value),
Object::toString, NumericParser.Long::parse),
Feature::getLong,
(name, value) -> Create.longFeature(name, (Long) value),
Object::toString, NumericParser.Long::parse),
FLOAT(7, Float.BYTES,
Feature::getFloat,
(name, value) -> Create.floatFeature(name, (Float) value),
Object::toString, Float::parseFloat),
Feature::getFloat,
(name, value) -> Create.floatFeature(name, (Float) value),
Object::toString, Float::parseFloat),
DOUBLE(8, Double.BYTES,
Feature::getDouble,
(name, value) -> Create.doubleFeature(name, (Double) value),
Object::toString, Double::parseDouble),
Feature::getDouble,
(name, value) -> Create.doubleFeature(name, (Double) value),
Object::toString, Double::parseDouble),

BIGINTEGER(9, VARIABLE_LENGTH,
Feature::getBigInteger,
(name, value) -> Create.bigIntegerFeature(name, (BigInteger) value),
Object::toString, BigInteger::new),
Feature::getBigInteger,
(name, value) -> Create.bigIntegerFeature(name, (BigInteger) value),
Object::toString, BigInteger::new),
BIGDECIMAL(10, VARIABLE_LENGTH,
Feature::getBigDecimal,
(name, value) -> Create.bigDecimalFeature(name, (BigDecimal) value),
Object::toString, BigDecimal::new),
Feature::getBigDecimal,
(name, value) -> Create.bigDecimalFeature(name, (BigDecimal) value),
Object::toString, BigDecimal::new),

DATE(11, Long.BYTES,
Feature::getDate,
(name, value) -> Create.dateFeature(name, (Date) value),
Feature::dateFormat, Feature::dateParse),
Feature::getDate,
(name, value) -> Create.dateFeature(name, (Date) value),
Feature::dateFormat, Feature::dateParse),

UUID(12, 2 * Long.BYTES,
Feature::getUUID,
(name, value) -> Create.uuidFeature(name, (java.util.UUID) value),
Object::toString, java.util.UUID::fromString);
Feature::getUUID,
(name, value) -> Create.uuidFeature(name, (java.util.UUID) value),
Object::toString, java.util.UUID::fromString);

final int fixedSize;
final int serialized;
Expand Down Expand Up @@ -426,17 +433,17 @@ public static Feature bigDecimalFeature(String name, BigDecimal value) {
notNull(value);
byte[] b = value.unscaledValue().toByteArray();
return new Feature(name, Type.BIGDECIMAL, ByteBuffer.allocate(Integer.BYTES + b.length)
.put(b)
.putInt(value.scale())
.array());
.put(b)
.putInt(value.scale())
.array());
}

public static Feature uuidFeature(String name, java.util.UUID value) {
notNull(value);
return new Feature(name, Type.UUID, ByteBuffer.allocate(2 * Long.BYTES)
.putLong(value.getLeastSignificantBits())
.putLong(value.getMostSignificantBits())
.array());
.putLong(value.getLeastSignificantBits())
.putLong(value.getMostSignificantBits())
.array());
}

public static Feature dateFeature(String name, Date value) {
Expand Down Expand Up @@ -470,7 +477,7 @@ public static Feature from(String s) {
public static Feature from(byte[] serialized) {
if (serialized.length < Integer.BYTES * 2) {
throw new IllegalArgumentException("Cannot load feature from a byte array that has "
+ serialized.length + " bytes which is < " + (2 * Integer.BYTES));
+ serialized.length + " bytes which is < " + (2 * Integer.BYTES));
}
var bb = ByteBuffer.wrap(serialized);
var typeSerialized = bb.getInt();
Expand Down Expand Up @@ -499,7 +506,7 @@ public static Feature from(byte[] serialized) {
}
if (bb.remaining() > 0) {
throw new IllegalArgumentException("Cannot load feature from a byte array that has "
+ serialized.length + " bytes which is " + bb.remaining() + " bytes too long");
+ serialized.length + " bytes which is " + bb.remaining() + " bytes too long");
}
final var name = new String(nameBuffer, StandardCharsets.UTF_8);
return new Feature(name, type, value);
Expand Down
4 changes: 2 additions & 2 deletions src/test/java/javax0/license3j/LicenseTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ void licenseSerializeAndDeserialize() {
final var restored = License.Create.from(buffer);
Assertions.assertEquals("Peter Verhas", restored.get("owner").getString());
Assertions.assertEquals(now, restored.get("expiry").getDate());
Assertions.assertEquals("expiry:DATE=2018-12-17 12:55:19.295\n" +
Assertions.assertEquals("expiry:DATE=2018-12-17 11:55:19.295\n" +
"owner=Peter Verhas\n" +
"template=<<null\n" +
"<<special template>>\n" +
Expand All @@ -64,7 +64,7 @@ void licenseStringifyAndDestringify() {
final var restored = License.Create.from(string);
Assertions.assertEquals("Peter Verhas", restored.get("owner").getString());
Assertions.assertEquals(now, restored.get("expiry").getDate());
Assertions.assertEquals("expiry:DATE=2018-12-17 12:55:19.295\n" +
Assertions.assertEquals("expiry:DATE=2018-12-17 11:55:19.295\n" +
"owner=Peter Verhas\n" +
"template=<<null\n" +
"<<special template>>\n" +
Expand Down
8 changes: 4 additions & 4 deletions src/test/java/javax0/license3j/TestFeature.java
Original file line number Diff line number Diff line change
Expand Up @@ -474,17 +474,17 @@ public void testBigDecimalFromString() {
@Test
@DisplayName("date feature is converted from string")
public void testDateFromString() {
final var sut1 = Feature.Create.from("name:DATE=2018-12-17 12:55:19.295");
final var sut1 = Feature.Create.from("name:DATE=2018-12-17 11:55:19.295");
Assertions.assertEquals("name", sut1.name());
Assertions.assertTrue(sut1.isDate());
Assertions.assertEquals(new Date(1545047719295L), sut1.getDate());

final var sut2 = Feature.Create.from("name:DATE=2018-12-17 12:55:19");
final var sut2 = Feature.Create.from("name:DATE=2018-12-17 11:55:19");
Assertions.assertEquals("name", sut2.name());
Assertions.assertTrue(sut2.isDate());
Assertions.assertEquals(new Date(1545047719000L), sut2.getDate());

final var sut3 = Feature.Create.from("name:DATE=2018-12-17 12:55");
final var sut3 = Feature.Create.from("name:DATE=2018-12-17 11:55");
Assertions.assertEquals("name", sut3.name());
Assertions.assertTrue(sut3.isDate());
Assertions.assertEquals(new Date(1545047700000L), sut3.getDate());
Expand Down Expand Up @@ -560,7 +560,7 @@ public void testBigdecimalToString() {
@DisplayName("Date feature is converted to string")
public void testDateToString() {
final var sut = Feature.Create.dateFeature("now", new Date(1545047719295L));
Assertions.assertEquals("now:DATE=2018-12-17 12:55:19.295", sut.toString());
Assertions.assertEquals("now:DATE=2018-12-17 11:55:19.295", sut.toString());
}
}

24 changes: 12 additions & 12 deletions src/test/java/javax0/license3j/hardware/TestInterfaceSelector.java
Original file line number Diff line number Diff line change
Expand Up @@ -24,16 +24,16 @@ boolean isSpecial(NetworkInterface netIf) {
};
}

private static IfTest test(final String ifName) throws NoSuchFieldException, InstantiationException, IllegalAccessException, InvocationTargetException {
private static IfTest test(final String ifName) throws NoSuchFieldException, InstantiationException, IllegalAccessException, InvocationTargetException, NoSuchMethodException {
return new IfTest(ifName);
}

private static NetworkInterface mockInterface(final String name)
throws NoSuchFieldException,
IllegalAccessException,
InvocationTargetException,
InstantiationException {
Constructor constructor = NetworkInterface.class.getDeclaredConstructors()[0];
InstantiationException, NoSuchMethodException {
Constructor constructor = NetworkInterface.class.getDeclaredConstructor(new Class[0]);
constructor.setAccessible(true);
NetworkInterface ni = (NetworkInterface) constructor.newInstance();
Field field = NetworkInterface.class.getDeclaredField("displayName");
Expand All @@ -44,51 +44,51 @@ private static NetworkInterface mockInterface(final String name)

@Test
@DisplayName("If there is no regular expression defined as allowed nor as denied then everything is allowed")
public void testJustAnyName() throws InvocationTargetException, InstantiationException, IllegalAccessException, NoSuchFieldException {
public void testJustAnyName() throws InvocationTargetException, InstantiationException, IllegalAccessException, NoSuchFieldException, NoSuchMethodException {
test("just-any-name").isUsable();
}

@Test
@DisplayName("If there is a regular expression allowing an interface then an interface matching the regex will be allowed")
public void explicitlyAllowed() throws InvocationTargetException, InstantiationException, IllegalAccessException, NoSuchFieldException {
public void explicitlyAllowed() throws InvocationTargetException, InstantiationException, IllegalAccessException, NoSuchFieldException, NoSuchMethodException {
test("allowed").allowed("allowed").isUsable();
}

@Test
@DisplayName("If there is a regular expression allowing an interface then an interface NOT matching the regex will be denied")
public void explicitlyNotAllowed() throws InvocationTargetException, InstantiationException, IllegalAccessException, NoSuchFieldException {
public void explicitlyNotAllowed() throws InvocationTargetException, InstantiationException, IllegalAccessException, NoSuchFieldException, NoSuchMethodException {
test("not allowed").allowed("allowed").isDenied();
}

@Test
@DisplayName("If there is a regular expression denying an interface then an interface matching the regex will be denied")
public void explicitlyDenied() throws InvocationTargetException, InstantiationException, IllegalAccessException, NoSuchFieldException {
public void explicitlyDenied() throws InvocationTargetException, InstantiationException, IllegalAccessException, NoSuchFieldException, NoSuchMethodException {
test("denied").allowed("allowed").denied("denied").isDenied();
}

@Test
@DisplayName("If there is a regular expression denying an interface then an interface matching the regex will be denied EVEN if it matches an regex allowing it")
public void explicitlyDeniedEvenIfAllowed() throws InvocationTargetException, InstantiationException, IllegalAccessException, NoSuchFieldException {
public void explicitlyDeniedEvenIfAllowed() throws InvocationTargetException, InstantiationException, IllegalAccessException, NoSuchFieldException, NoSuchMethodException {
test("denied").allowed("denied").denied("denied").isDenied();
}

@Test
@DisplayName("If there is a regular expression allowing it and the denying regex does not match then it is allowed")
public void explicitlyAllowedNotDenied() throws InvocationTargetException, InstantiationException, IllegalAccessException, NoSuchFieldException {
public void explicitlyAllowedNotDenied() throws InvocationTargetException, InstantiationException, IllegalAccessException, NoSuchFieldException, NoSuchMethodException {
test("allowed").allowed("allowed").denied("denied").isUsable();
}

@Test
@DisplayName("If there is a regular expression allowing it and the denying regexes do not match then it is allowed")
public void explicitlyAllowedNotDeniedByAny() throws InvocationTargetException, InstantiationException, IllegalAccessException, NoSuchFieldException {
test("allowed").allowed("allowed").denied("denied","denied2").isUsable();
public void explicitlyAllowedNotDeniedByAny() throws InvocationTargetException, InstantiationException, IllegalAccessException, NoSuchFieldException, NoSuchMethodException {
test("allowed").allowed("allowed").denied("denied", "denied2").isUsable();
}

private static class IfTest {
NetworkInterface ni;
Network.Interface.Selector sut;

IfTest(String name) throws InvocationTargetException, InstantiationException, IllegalAccessException, NoSuchFieldException {
IfTest(String name) throws InvocationTargetException, InstantiationException, IllegalAccessException, NoSuchFieldException, NoSuchMethodException {
ni = mockInterface(name);
sut = newSut();
}
Expand Down

0 comments on commit 6d34ed2

Please sign in to comment.