diff --git a/javadoc-crawler/src/main/java/io/opentelemetry/javadocs/JavaDocsCrawler.java b/javadoc-crawler/src/main/java/io/opentelemetry/javadocs/JavaDocsCrawler.java index 2907885297b..c3f919474ec 100644 --- a/javadoc-crawler/src/main/java/io/opentelemetry/javadocs/JavaDocsCrawler.java +++ b/javadoc-crawler/src/main/java/io/opentelemetry/javadocs/JavaDocsCrawler.java @@ -12,6 +12,7 @@ import java.net.http.HttpRequest; import java.net.http.HttpResponse; import java.util.ArrayList; +import java.util.Comparator; import java.util.List; import java.util.Locale; import java.util.Map; @@ -32,11 +33,11 @@ public final class JavaDocsCrawler { // latest periodically to avoid crawling artifacts that stopped being published. private static final Map GROUPS_AND_MIN_VERSION = Map.of( - "io.opentelemetry", "1.49.0", - "io.opentelemetry.instrumentation", "2.15.0", - "io.opentelemetry.contrib", "1.46.0", - "io.opentelemetry.semconv", "1.32.0", - "io.opentelemetry.proto", "1.3.2"); + "io.opentelemetry", "1.60.1", + "io.opentelemetry.instrumentation", "2.25.0", + "io.opentelemetry.contrib", "1.54.0", + "io.opentelemetry.semconv", "1.40.0", + "io.opentelemetry.proto", "1.10.0"); private static final String MAVEN_CENTRAL_BASE_URL = "https://search.maven.org/solrsearch/select?q=g:"; @@ -159,9 +160,10 @@ static List crawlJavaDocs( HttpClient client, String minVersion, List artifacts) throws IOException, InterruptedException { List updatedArtifacts = new ArrayList<>(); + SemanticVersion minSemanticVersion = SemanticVersion.parse(minVersion); for (Artifact artifact : artifacts) { - if (artifact.getVersion().compareTo(minVersion) < 0) { + if (SemanticVersion.parse(artifact.getVersion()).compareTo(minSemanticVersion) < 0) { logger.info( String.format( "Skipping crawling %s due to version %s being less than minVersion %s", @@ -209,5 +211,103 @@ static List crawlJavaDocs( return updatedArtifacts; } + static final class SemanticVersion implements Comparable { + private static final Comparator> CORE_VERSION_COMPARATOR = + (left, right) -> { + for (int i = 0; i < Math.max(left.size(), right.size()); i++) { + int leftPart = i < left.size() ? left.get(i) : 0; + int rightPart = i < right.size() ? right.get(i) : 0; + int result = Integer.compare(leftPart, rightPart); + if (result != 0) { + return result; + } + } + return 0; + }; + + private final List coreVersionParts; + private final String qualifier; + + private SemanticVersion(List coreVersionParts, String qualifier) { + this.coreVersionParts = coreVersionParts; + this.qualifier = qualifier; + } + + static SemanticVersion parse(String version) { + String[] versionParts = version.split("-", 2); + List coreVersionParts = new ArrayList<>(); + for (String part : versionParts[0].split("\\.")) { + coreVersionParts.add(Integer.parseInt(part)); + } + String qualifier = versionParts.length == 2 ? versionParts[1] : ""; + return new SemanticVersion(coreVersionParts, qualifier); + } + + @Override + public int compareTo(SemanticVersion other) { + int coreVersionResult = + CORE_VERSION_COMPARATOR.compare(coreVersionParts, other.coreVersionParts); + if (coreVersionResult != 0) { + return coreVersionResult; + } + if (qualifier.isEmpty() && other.qualifier.isEmpty()) { + return 0; + } + if (qualifier.isEmpty()) { + return 1; + } + if (other.qualifier.isEmpty()) { + return -1; + } + return compareQualifier(qualifier, other.qualifier); + } + + private static int compareQualifier(String leftQualifier, String rightQualifier) { + String[] leftParts = leftQualifier.split("\\."); + String[] rightParts = rightQualifier.split("\\."); + + for (int i = 0; i < Math.max(leftParts.length, rightParts.length); i++) { + if (i >= leftParts.length) { + return -1; + } + if (i >= rightParts.length) { + return 1; + } + + String leftIdentifier = leftParts[i]; + String rightIdentifier = rightParts[i]; + if (leftIdentifier.equals(rightIdentifier)) { + continue; + } + + boolean leftNumeric = isNumericIdentifier(leftIdentifier); + boolean rightNumeric = isNumericIdentifier(rightIdentifier); + if (leftNumeric && rightNumeric) { + if (leftIdentifier.length() != rightIdentifier.length()) { + return Integer.compare(leftIdentifier.length(), rightIdentifier.length()); + } + return leftIdentifier.compareTo(rightIdentifier); + } + if (leftNumeric != rightNumeric) { + return leftNumeric ? -1 : 1; + } + return leftIdentifier.compareTo(rightIdentifier); + } + return 0; + } + + private static boolean isNumericIdentifier(String identifier) { + if (identifier.isEmpty()) { + return false; + } + for (int i = 0; i < identifier.length(); i++) { + if (!Character.isDigit(identifier.charAt(i))) { + return false; + } + } + return true; + } + } + private JavaDocsCrawler() {} } diff --git a/javadoc-crawler/src/test/java/io/opentelemetry/javadocs/JavaDocsCrawlerTest.java b/javadoc-crawler/src/test/java/io/opentelemetry/javadocs/JavaDocsCrawlerTest.java index 09a3b17e320..92150e0949f 100644 --- a/javadoc-crawler/src/test/java/io/opentelemetry/javadocs/JavaDocsCrawlerTest.java +++ b/javadoc-crawler/src/test/java/io/opentelemetry/javadocs/JavaDocsCrawlerTest.java @@ -8,6 +8,7 @@ import static io.opentelemetry.javadocs.JavaDocsCrawler.JAVA_DOC_DOWNLOADED_TEXT; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -91,4 +92,31 @@ void testCrawler() throws IOException, InterruptedException { "https://javadoc.io/doc/io.opentelemetry/opentelemetry-context/1.49.0/opentelemetry/context/package-summary.html"); assertThat(updated).containsExactly(artifact); } + + @Test + void compareVersionsUsesSemanticOrdering() { + assertThat(compareVersions("1.9.0", "1.49.0")).isLessThan(0); + assertThat(compareVersions("1.49.0", "1.49.0")).isZero(); + assertThat(compareVersions("1.60.0", "1.49.0")).isGreaterThan(0); + assertThat(compareVersions("1.60.0-alpha", "1.60.0")).isLessThan(0); + assertThat(compareVersions("1.0.0-rc.2", "1.0.0-rc.10")).isLessThan(0); + assertThat(compareVersions("1.0.0-rc.10", "1.0.0")).isLessThan(0); + } + + @Test + void crawlSkipsArtifactsBelowMinVersionUsingSemanticComparison() + throws IOException, InterruptedException { + Artifact oldArtifact = new Artifact("io.opentelemetry", "opentelemetry-context", "1.9.0"); + + List updated = + JavaDocsCrawler.crawlJavaDocs(mockClient, "1.49.0", List.of(oldArtifact)); + + verify(mockClient, never()).send(any(HttpRequest.class), any()); + assertThat(updated).isEmpty(); + } + + private static int compareVersions(String left, String right) { + return JavaDocsCrawler.SemanticVersion.parse(left) + .compareTo(JavaDocsCrawler.SemanticVersion.parse(right)); + } }