Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(semver): replace semver library with translation from node-semver #21

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 2 additions & 6 deletions 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.aws.greengrass</groupId>
<artifactId>component-common</artifactId>
<version>2.1.1-SNAPSHOT</version>
<version>2.2.0-SNAPSHOT</version>
<packaging>jar</packaging>

<licenses>
Expand All @@ -31,11 +31,6 @@
<artifactId>jackson-dataformat-yaml</artifactId>
<version>2.13.2</version>
</dependency>
<dependency>
<groupId>com.vdurmont</groupId>
<artifactId>semver4j</artifactId>
<version>3.1.0</version>
</dependency>
<dependency>
<groupId>org.hamcrest</groupId>
<artifactId>hamcrest</artifactId>
Expand Down Expand Up @@ -99,6 +94,7 @@
<excludes>
<exclude>*</exclude>
<exclude>src/*/resources/**</exclude>
<exclude>src/**/semver/**</exclude>
</excludes>
</licenseSet>
</licenseSets>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,20 +5,20 @@

package com.amazon.aws.iot.greengrass.component.common;

import com.amazon.aws.iot.greengrass.semver.SemVer;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.vdurmont.semver4j.Semver;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import lombok.Builder;
import lombok.NonNull;
import lombok.Value;

import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

@JsonDeserialize(builder = ComponentRecipe.ComponentRecipeBuilder.class)
@Value
Expand All @@ -37,7 +37,7 @@ public class ComponentRecipe {

@NonNull
@JsonSerialize(using = SemverSerializer.class)
Semver componentVersion;
SemVer componentVersion;

@Builder.Default
ComponentType componentType = ComponentType.GENERIC;
Expand Down Expand Up @@ -100,17 +100,17 @@ public ComponentRecipeBuilder componentName(String name) {
}
}

public ComponentRecipeBuilder componentVersion(Semver version) {
public ComponentRecipeBuilder componentVersion(SemVer version) {
if (version == null) {
throw new NullPointerException("Component version is null");
} else {
if (version.getValue().length() > COMPONENT_VERSION_LENGTH) {
throw new IllegalArgumentException(String.format("Component version length exceeds %d characters",
COMPONENT_VERSION_LENGTH));
}
if (version.getMajor() > COMPONENT_MAX_VERSION_NUMBER
|| version.getMinor() > COMPONENT_MAX_VERSION_NUMBER
|| version.getPatch() > COMPONENT_MAX_VERSION_NUMBER) {
if (version.major > COMPONENT_MAX_VERSION_NUMBER
|| version.minor > COMPONENT_MAX_VERSION_NUMBER
|| version.patch > COMPONENT_MAX_VERSION_NUMBER) {
throw new IllegalArgumentException("Component version major, minor, patch can't exceed 6 digits");
}
this.componentVersion = version;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,10 @@
package com.amazon.aws.iot.greengrass.component.common;


import com.amazon.aws.iot.greengrass.semver.Range;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.vdurmont.semver4j.Requirement;
import lombok.Builder;
import lombok.NonNull;
import lombok.Value;
Expand All @@ -19,13 +19,13 @@
public class DependencyProperties {
@JsonSerialize(using = RequirementSerializer.class)
@NonNull
Requirement versionRequirement;
Range versionRequirement;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are Requirements and Ranges equivalent?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, that's the semver term for what the other library called a requirement.

Copy link
Member Author

@MikeDombo MikeDombo Mar 18, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd recommend downloading this branch and then writing test code to play around with it and check whatever you think is needed. Just a code review with this volume of code is unlikely to find anything.


DependencyType dependencyType;

@Builder
public DependencyProperties(@NonNull String versionRequirement, DependencyType dependencyType) {
this.versionRequirement = Requirement.buildNPM(versionRequirement);
this.versionRequirement = new Range(versionRequirement);
this.dependencyType = dependencyType == null ? DependencyType.HARD : dependencyType;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,20 +5,20 @@

package com.amazon.aws.iot.greengrass.component.common;

import com.amazon.aws.iot.greengrass.semver.Range;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.ser.std.StdSerializer;
import com.vdurmont.semver4j.Requirement;

import java.io.IOException;

public class RequirementSerializer extends StdSerializer<Requirement> {
public class RequirementSerializer extends StdSerializer<Range> {
public RequirementSerializer() {
super(Requirement.class);
super(Range.class);
}

@Override
public void serialize(final Requirement value, final JsonGenerator gen, final SerializerProvider provider) throws IOException {
public void serialize(final Range value, final JsonGenerator gen, final SerializerProvider provider) throws IOException {
final String version = value.toString();
gen.writeString(version);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,20 +5,20 @@

package com.amazon.aws.iot.greengrass.component.common;

import com.amazon.aws.iot.greengrass.semver.SemVer;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.ser.std.StdSerializer;
import com.vdurmont.semver4j.Semver;

import java.io.IOException;

public class SemverSerializer extends StdSerializer<Semver> {
public class SemverSerializer extends StdSerializer<SemVer> {
public SemverSerializer() {
super(Semver.class);
super(SemVer.class);
}

@Override
public void serialize(final Semver value, final JsonGenerator gen, final SerializerProvider provider) throws IOException {
public void serialize(final SemVer value, final JsonGenerator gen, final SerializerProvider provider) throws IOException {
final String version = value.getValue();
gen.writeString(version);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,10 @@
package com.amazon.aws.iot.greengrass.configuration.common;

import com.amazon.aws.iot.greengrass.component.common.SemverSerializer;
import com.amazon.aws.iot.greengrass.semver.SemVer;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.vdurmont.semver4j.Semver;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
Expand All @@ -24,7 +24,7 @@
public class ComponentUpdate {

@JsonSerialize(using = SemverSerializer.class)
private Semver version;
private SemVer version;

private ConfigurationUpdate configurationUpdate;

Expand Down
177 changes: 177 additions & 0 deletions src/main/java/com/amazon/aws/iot/greengrass/semver/Comparator.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
/*
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*
* Translated from node-semver:
*
* The ISC License
* Copyright (c) Isaac Z. Schlueter and Contributors
*
* See com.amazon.aws.iot.greengrass.semver.THIRD-PARTY-LICENSE
*/

package com.amazon.aws.iot.greengrass.semver;

import lombok.EqualsAndHashCode;

import java.util.Objects;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

@EqualsAndHashCode
public class Comparator {
public final String value;
private String operator;
public SemVer semver;
public static final AnySemVer ANY = Comparator.AnySemVer.ANY;

public Comparator(String comp) {
this.parse(comp);

if (this.semver == ANY) {
this.value = "";
} else {
this.value = this.operator + this.semver.getVersion();
}
}

private void parse (String comp) {
Pattern r = SemVerRegexes.re.get(SemVerRegexes.t.get("COMPARATOR"));
Matcher m = r.matcher(comp);

if (!m.find()) {
throw new IllegalArgumentException("Invalid comparator " + comp);
}

this.operator = m.group(1) != null ? m.group(1) : "";
if (this.operator.equals("=")) {
this.operator = "";
}

// if it literally is just '>' or '' then allow anything.
if (m.group(2) == null) {
this.semver = ANY;
} else {
this.semver = new SemVer(m.group(2));
}
}

public boolean test (Object version) {
if (this.semver == ANY || version == ANY) {
return true;
}

if (version instanceof String) {
try {
version = new SemVer((String) version);
} catch (RuntimeException e) {
return false;
}
}

return cmp((SemVer) version, this.operator, this.semver);
}

public static boolean cmp(SemVer a, String op, SemVer b) {
switch (op) {
case "===":
return a.getVersion().equals(b.getVersion());
case "!==":
return !a.getVersion().equals(b.getVersion());
case "":
case "=":
case "==":
return eq(a, b);
case "!=":
return neq(a, b);
case ">":
return gt(a, b);
case ">=":
return gte(a, b);
case "<":
return lt(a, b);
case "<=":
return lte(a, b);
default:
throw new IllegalArgumentException("Invalid operator " + op);
}
}

public static boolean eq(SemVer a, SemVer b){
return a.compareTo(b) == 0;
}

public static boolean neq(SemVer a, SemVer b){
return a.compareTo(b) != 0;
}

public static boolean gt(SemVer a, SemVer b){
return a.compareTo(b) > 0;
}

public static boolean gte(SemVer a, SemVer b){
return a.compareTo(b) >= 0;
}

public static boolean lt(SemVer a, SemVer b){
return a.compareTo(b) < 0;
}

public static boolean lte(SemVer a, SemVer b){
return a.compareTo(b) <= 0;
}

public boolean intersects (Comparator comp) {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Recommendation generated by Amazon CodeGuru Reviewer. Leave feedback on this recommendation by replying to the comment or by reacting to the comment using emoji.

The cyclomatic complexity of this method is 22. By comparison, 99% of the methods in the CodeGuru reference dataset have a lower cyclomatic complexity. This indicates the method has a high number of decisions and it can make the logic difficult to understand and test.

We recommend that you simplify this method or break it into multiple methods. For example, consider extracting the code block on lines 125-135 into a separate method.

if (this.operator.equals("")) {
if (this.value.equals("")) {
return true;
}
return new Range(comp.value).test(this.semver);
} else if (comp.operator.equals("")) {
if (comp.value.equals("")) {
return true;
}
return new Range(this.value).test(comp.semver);
}

boolean sameDirectionIncreasing =
(this.operator.equals(">=") || this.operator.equals(">")) &&
(comp.operator.equals(">=") || comp.operator.equals(">"));
boolean sameDirectionDecreasing =
(this.operator.equals("<=") || this.operator.equals("<")) &&
(comp.operator.equals("<=") || comp.operator.equals("<"));
boolean sameSemVer = Objects.equals(this.semver.getVersion(), comp.semver.getVersion());
boolean differentDirectionsInclusive =
(this.operator.equals(">=") || this.operator.equals("<=")) &&
(comp.operator.equals(">=") || comp.operator.equals("<="));
boolean oppositeDirectionsLessThan =
cmp(this.semver, "<", comp.semver) &&
(Objects.equals(this.operator, ">=") || Objects.equals(this.operator, ">")) &&
(Objects.equals(comp.operator, "<=") || Objects.equals(comp.operator, "<"));
boolean oppositeDirectionsGreaterThan =
cmp(this.semver, ">", comp.semver) &&
(Objects.equals(this.operator, "<=") || Objects.equals(this.operator, "<")) &&
(Objects.equals(comp.operator, ">=") || Objects.equals(comp.operator, ">"));

return (
sameDirectionIncreasing ||
sameDirectionDecreasing ||
(sameSemVer && differentDirectionsInclusive) ||
oppositeDirectionsLessThan ||
oppositeDirectionsGreaterThan
);
}

@Override
public String toString() {
return this.value;
}

public static class AnySemVer extends SemVer {
final static AnySemVer ANY = new AnySemVer();
private AnySemVer() {
super("0.0.0");
}
}
}

Loading