Skip to content

Commit

Permalink
fix (model): Introduce GAVR abstraction
Browse files Browse the repository at this point in the history
  • Loading branch information
vorburger committed Feb 8, 2025
1 parent 63cd4f3 commit 920fc46
Show file tree
Hide file tree
Showing 3 changed files with 227 additions and 35 deletions.
183 changes: 183 additions & 0 deletions java/dev/enola/model/enola/maven/connect/mima/GAVR.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
/*
* SPDX-License-Identifier: Apache-2.0
*
* Copyright 2025 The Enola <https://enola.dev> Authors
*
* Licensed 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
*
* https://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 dev.enola.model.enola.maven.connect.mima;

import static com.google.common.base.Strings.isNullOrEmpty;

import org.eclipse.aether.artifact.Artifact;
import org.eclipse.aether.artifact.DefaultArtifact;

/**
* GAVR is a Maven GroupID, ArtifactID, Version, Extension (AKA Type), Classifier + Repository.
*
* <p>The GroupID, ArtifactID & Version are mandatory and cannot be empty. The Extension, Classifier
* & Repository can be empty, but never null.
*
* <p>This class itself does NOT imply any "defaults" for Extension, Classifier & Repository; but
* it's users may well.
*/
public record GAVR(
String groupId,
String artifactId,
String extension,
String classifier,
String version,
String repo) {

// TODO Consider #performance - make this a class to cache Gradle & PkgURL representations?

/**
* Parse a "short Gradle-style" GAV in the
* <groupId>:<artifactId>[:<extension>[:<classifier>]]:<version> format, like e.g.
* "ch.vorburger.mariaDB4j:mariaDB4j-core:3.1.0" to a GAVR. NB: This syntax does not allow
* specifying a repository!
*/
public static GAVR parseGradle(String gav) {
var artifact = new DefaultArtifact(gav);
return new GAVR(
artifact.getGroupId(),
artifact.getArtifactId(),
artifact.getExtension(),
artifact.getClassifier(),
artifact.getVersion(),
"");
}

// TODO public GAVR parsePkgURL(String purl), with
// https://github.com/package-url/packageurl-java

public static class Builder {
private String groupId;
private String artifactId;
private String extension;
private String classifier;
private String version;
private String repo;

public Builder groupId(String groupId) {
this.groupId = groupId;
return this;
}

public Builder artifactId(String artifactId) {
this.artifactId = artifactId;
return this;
}

public Builder extension(String extension) {
this.extension = extension;
return this;
}

public Builder classifier(String classifier) {
this.classifier = classifier;
return this;
}

public Builder version(String version) {
this.version = version;
return this;
}

public Builder repo(String repo) {
this.repo = repo;
return this;
}

public GAVR build() {
return new GAVR(
groupId,
artifactId,
nullToEmpty(extension),
nullToEmpty(classifier),
version,
nullToEmpty(repo));
}

private String nullToEmpty(String string) {
if (string == null) return "";
else return string;
}
}

public GAVR {
requireNonEmpty(groupId, "groupId");
requireNonEmpty(artifactId, "artifactId");
requireNonNull(extension, "extension");
requireNonNull(classifier, "classifier");
requireNonEmpty(version, "version");
requireNonNull(repo, "repo");
}

private void requireNonNull(Object object, String field) {
if (object == null) throw new IllegalStateException(field + " cannot be null");
}

private void requireNonEmpty(String string, String field) {
if (isNullOrEmpty(string))
throw new IllegalStateException(field + " cannot be null or empty");
}

/** Return a String in the same format that {@link #parseGradle(String)} uses. */
@SuppressWarnings("StringBufferReplaceableByString") // pre-sizing is more efficient (?)
public String toGradle() {
var sb = // w.o. repo.length()
new StringBuilder(
4 // max. 4x ':'
+ groupId.length()
+ artifactId.length()
+ extension.length()
+ classifier.length()
+ version.length());

sb.append(groupId);
sb.append(':');
sb.append(artifactId);

if (!extension.isEmpty()) {
sb.append(':');
sb.append(extension);
}

if (!classifier.isEmpty()) {
sb.append(':');
sb.append(classifier);
}

sb.append(':');
sb.append(version);
return sb.toString();
}

// TODO String toPkgURL()

public Builder toBuilder() {
return new Builder()
.groupId(groupId)
.artifactId(artifactId)
.extension(extension)
.classifier(classifier)
.version(version);
}

// NOT public - this is a package private internal method!
// NB: Artifact does NOT include the Repository! Callers will use repository() to obtain that.
Artifact toArtifact() {
return new DefaultArtifact(groupId, artifactId, classifier, extension, version);
}
}
65 changes: 38 additions & 27 deletions java/dev/enola/model/enola/maven/connect/mima/Mima.java
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@

import org.apache.maven.model.Model;
import org.apache.maven.model.io.xpp3.MavenXpp3Writer;
import org.eclipse.aether.artifact.DefaultArtifact;
import org.eclipse.aether.collection.CollectRequest;
import org.eclipse.aether.graph.Dependency;
import org.eclipse.aether.graph.DependencyNode;
Expand All @@ -57,20 +56,13 @@ public class Mima implements AutoCloseable {
private static final String ENOLA_DEFAULT_CHECKSUM_POLICY_STRING =
ENOLA_DEFAULT_CHECKSUM_POLICY.name().toLowerCase(Locale.ROOT);

public static final RemoteRepository CENTRAL = ContextOverrides.CENTRAL;
public static final RemoteRepository JITPACK =
new RemoteRepository.Builder("jitpack", "default", "https://jitpack.io/")
.setReleasePolicy(
new RepositoryPolicy(
true,
RepositoryPolicy.UPDATE_POLICY_NEVER,
ENOLA_DEFAULT_CHECKSUM_POLICY_STRING))
.setSnapshotPolicy(
new RepositoryPolicy(
false,
RepositoryPolicy.UPDATE_POLICY_NEVER,
ENOLA_DEFAULT_CHECKSUM_POLICY_STRING))
.build();
/**
* This CENTRAL uses FAIL instead of CHECKSUM_POLICY_WARN like ContextOverrides.CENTRAL does!
*/
public static final RemoteRepository CENTRAL =
getRemoteRepository(ContextOverrides.CENTRAL.getUrl());

public static final RemoteRepository JITPACK = getRemoteRepository("https://jitpack.io/");

private static final Logger logger = LoggerFactory.getLogger(Mima.class);

Expand Down Expand Up @@ -118,38 +110,42 @@ public void close() {
/**
* Fetch a Maven Model from (remote) repositories, given a GAV.
*
* @param gav a "Gradle-style" GAV in the
* <groupId>:<artifactId>[:<extension>[:<classifier>]]:<version> format, like e.g.
* "ch.vorburger.mariaDB4j:mariaDB4j-core:3.1.0"
* @param gavr a {@link GAVR}
* @return a {@link ModelResponse}, of which you typically care about the {@link
* ModelResponse#getEffectiveModel()}
*/
public ModelResponse get(String gav)
public ModelResponse get(GAVR gavr)
throws ArtifactResolutionException,
VersionResolutionException,
ArtifactDescriptorException {
var artifact = new DefaultArtifact(gav);
var artifact = gavr.toArtifact();
var request =
ModelRequest.builder()
.setArtifact(artifact)
// TODO https://github.com/maveniverse/mima/issues/166
// .setRepository(gavr.repo())
// TODO What is RequestContext really used for?!
.setRequestContext(gav)
.setRequestContext(gavr.toString())
.build();
var response = mmr.readModel(request);
if (response == null) throw new IllegalArgumentException(gav);
if (response == null) throw new IllegalArgumentException(gavr.toGradle());
return response;
}

// TODO public ModelResponse get(RemoteRepository repo, String gav)

// Utilities with access to state of this class

public DependencyNode collect(String gav) throws DependencyResolutionException {
var artifact = new DefaultArtifact(gav);
public DependencyNode collect(GAVR gavr) throws DependencyResolutionException {
var artifact = gavr.toArtifact();
Dependency dependency = new Dependency(artifact, "runtime");
CollectRequest collectRequest = new CollectRequest();
collectRequest.setRoot(dependency);
collectRequest.setRepositories(context.remoteRepositories());

if (gavr.repo().isEmpty()) collectRequest.setRepositories(context.remoteRepositories());
else {
var remoteRepository = getRemoteRepository(gavr.repo());
// NOT addRepository() but setRepositories(), to clear any existing:
collectRequest.setRepositories(List.of(remoteRepository));
}

DependencyRequest dependencyRequest = new DependencyRequest();
dependencyRequest.setCollectRequest(collectRequest);
Expand All @@ -161,6 +157,21 @@ public DependencyNode collect(String gav) throws DependencyResolutionException {

// Utilities that are purely static "extension" helper methods

private static RemoteRepository getRemoteRepository(String url) {
return new RemoteRepository.Builder(url, "default", url)
.setReleasePolicy(
new RepositoryPolicy(
true,
RepositoryPolicy.UPDATE_POLICY_NEVER,
ENOLA_DEFAULT_CHECKSUM_POLICY_STRING))
.setSnapshotPolicy(
new RepositoryPolicy(
false,
RepositoryPolicy.UPDATE_POLICY_NEVER,
ENOLA_DEFAULT_CHECKSUM_POLICY_STRING))
.build();
}

public static String classpath(DependencyNode root) throws DependencyResolutionException {
PreorderNodeListGenerator nlg = new PreorderNodeListGenerator();
root.accept(nlg);
Expand Down
14 changes: 6 additions & 8 deletions java/dev/enola/model/enola/maven/connect/mima/MimaTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -31,16 +31,14 @@ public class MimaTest {

// TODO Allow explicit repo in get(), see https://github.com/maveniverse/mima/issues/166

// TODO class/record GAVR (with repoS), instead String
// TODO interface Artifact extends Thing, set Dependencies & Parent etc.

// TODO Dependencies & Parent

// TODO interface Artifact extends Thing
// TODO Improve test coverage with a local repo server - is that worth it?!

@Test
public void mariaDB4j() throws RepositoryException {
try (var mima = new Mima()) {
var gav = "ch.vorburger.mariaDB4j:mariaDB4j-core:3.1.0";
var gav = GAVR.parseGradle("ch.vorburger.mariaDB4j:mariaDB4j-core:3.1.0");
var response = mima.get(gav);
var model = response.getEffectiveModel();
assertThat(model).isNotNull();
Expand All @@ -64,7 +62,7 @@ public void mariaDB4j() throws RepositoryException {

@Test
public void jitpack() throws RepositoryException {
var gav = "com.github.vorburger:java-multihash:ed14893c86";
var gav = GAVR.parseGradle("com.github.vorburger:java-multihash:ed14893c86");
try (var mima = new Mima(List.of(Mima.JITPACK))) {
assertThat(mima.get(gav)).isNotNull();
}
Expand All @@ -77,14 +75,14 @@ public void jitpack() throws RepositoryException {
@Test(expected = ArtifactResolutionException.class)
public void nonExistingVersion() throws RepositoryException {
try (var mima = new Mima()) {
mima.get("ch.vorburger.mariaDB4j:mariaDB4j-core:1.0.0");
mima.get(GAVR.parseGradle("ch.vorburger.mariaDB4j:mariaDB4j-core:1.0.0"));
}
}

@Test(expected = IllegalArgumentException.class)
public void gavWithoutVersion() throws RepositoryException {
try (var mima = new Mima()) {
mima.get("ch.vorburger.mariaDB4j:mariaDB4j-core");
mima.get(GAVR.parseGradle("ch.vorburger.mariaDB4j:mariaDB4j-core"));
}
}
}

0 comments on commit 920fc46

Please sign in to comment.