250 lines
12 KiB
Java
250 lines
12 KiB
Java
package io.gitlab.jfronny.inceptum.common.api;
|
|
|
|
import io.gitlab.jfronny.commons.HttpUtils;
|
|
import io.gitlab.jfronny.inceptum.common.*;
|
|
import io.gitlab.jfronny.inceptum.common.model.maven.MavenDependency;
|
|
import io.gitlab.jfronny.inceptum.common.model.maven.Pom;
|
|
import org.jetbrains.annotations.Nullable;
|
|
import org.w3c.dom.*;
|
|
import org.xml.sax.SAXException;
|
|
|
|
import javax.xml.parsers.*;
|
|
import javax.xml.stream.XMLStreamException;
|
|
import java.io.IOException;
|
|
import java.io.InputStream;
|
|
import java.net.URISyntaxException;
|
|
import java.nio.file.Path;
|
|
import java.util.*;
|
|
|
|
public class MavenApi {
|
|
private static final DocumentBuilder FACTORY;
|
|
private static final Set<String> RUNTIME_SCOPES = Set.of("compile", "runtime");
|
|
|
|
static {
|
|
try {
|
|
FACTORY = DocumentBuilderFactory.newInstance().newDocumentBuilder();
|
|
} catch (ParserConfigurationException e) {
|
|
throw new RuntimeException("Could not create document builder", e);
|
|
}
|
|
}
|
|
|
|
public static Path downloadLibrary(String repo, Pom pom) throws IOException, URISyntaxException {
|
|
String artifact = pom.groupId + ':' + pom.artifactId + ':' + pom.version;
|
|
String rawArtifact = artifact;
|
|
if (pom.classifier != null || pom.snapshotVersion != null) {
|
|
artifact += ':';
|
|
if (pom.snapshotVersion != null) artifact += pom.snapshotVersion;
|
|
if (pom.classifier != null) {
|
|
if (pom.snapshotVersion != null) artifact += '-';
|
|
artifact += pom.classifier;
|
|
}
|
|
}
|
|
if (pom.classifier != null) rawArtifact += ':' + pom.classifier;
|
|
Path res = MetaHolder.LIBRARIES_DIR.resolve(mavenNotationToJarPath(rawArtifact));
|
|
Net.downloadFile(Utils.join("/", repo, mavenNotationToJarPath(artifact)), res);
|
|
return res;
|
|
}
|
|
|
|
public static Pom getPom(String repo, String artifact) throws IOException, SAXException, URISyntaxException, XMLStreamException {
|
|
try (InputStream is = HttpUtils.get(Utils.join("/", repo, mavenNotationToPomPath(artifact))).sendInputStream()) {
|
|
Document doc = FACTORY.parse(is);
|
|
doc.getDocumentElement().normalize();
|
|
Pom result = new Pom();
|
|
if (!"project".equals(doc.getDocumentElement().getNodeName())) throw new IOException("Illegal document name");
|
|
boolean hasModelVersion = false;
|
|
boolean hasGroupId = false;
|
|
boolean hasArtifactId = false;
|
|
boolean hasVersion = false;
|
|
for (Node node : iterable(doc.getDocumentElement().getChildNodes())) {
|
|
switch (node.getNodeName()) {
|
|
case "modelVersion" -> {
|
|
hasModelVersion = true;
|
|
result.modelVersion = node.getTextContent();
|
|
}
|
|
case "parent" -> {
|
|
// Dirty hack to get slf4j working: simply assume the groupId and version of the parent is also the groupId of this
|
|
if (!hasGroupId) {
|
|
for (Node child : iterable(node.getChildNodes())) {
|
|
switch (child.getNodeName()) {
|
|
case "groupId" -> {
|
|
if (!hasGroupId) {
|
|
hasGroupId = true;
|
|
result.groupId = node.getTextContent();
|
|
}
|
|
}
|
|
case "version" -> {
|
|
if (!hasVersion) {
|
|
hasVersion = true;
|
|
result.version = node.getTextContent();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
case "groupId" -> {
|
|
hasGroupId = true;
|
|
result.groupId = node.getTextContent();
|
|
}
|
|
case "artifactId" -> {
|
|
hasArtifactId = true;
|
|
result.artifactId = node.getTextContent();
|
|
}
|
|
case "version" -> {
|
|
hasVersion = true;
|
|
result.version = node.getTextContent();
|
|
}
|
|
case "packaging" -> result.packaging = node.getTextContent();
|
|
case "dependencies" -> {
|
|
result.dependencies = new LinkedList<>();
|
|
for (Node dep : iterable(node.getChildNodes())) {
|
|
MavenDependency resolved = parseDependency(dep);
|
|
if (resolved != null) {
|
|
result.dependencies.add(resolved);
|
|
}
|
|
}
|
|
}
|
|
case "classifier" -> result.classifier = node.getTextContent();
|
|
case "versioning" -> {
|
|
for (Node node1 : iterable(node.getChildNodes())) {
|
|
if (node1.getNodeName().equals("snapshot")) {
|
|
String timestamp = null;
|
|
String buildNumber = null;
|
|
for (Node node2 : iterable(node1.getChildNodes())) {
|
|
switch (node2.getNodeName()) {
|
|
case "timestamp" -> timestamp = node2.getTextContent();
|
|
case "buildNumber" -> buildNumber = node2.getTextContent();
|
|
default -> {}
|
|
}
|
|
}
|
|
if (timestamp == null) throw new IOException("Pom snapshots lack timestamp");
|
|
if (buildNumber == null) throw new IOException("Pom snapshots lack buildNumber");
|
|
result.snapshotVersion = timestamp + '-' + buildNumber;
|
|
}
|
|
}
|
|
}
|
|
default -> {}
|
|
}
|
|
}
|
|
if (!hasModelVersion) throw new IOException("Pom lacks modelVersion");
|
|
if (!hasGroupId) throw new IOException("Pom lacks groupId");
|
|
if (!hasArtifactId) throw new IOException("Pom lacks artifactId");
|
|
if (!hasVersion) throw new IOException("Pom lacks version");
|
|
return result;
|
|
}
|
|
}
|
|
|
|
private static @Nullable MavenDependency parseDependency(Node doc) throws IOException {
|
|
MavenDependency result = new MavenDependency();
|
|
boolean hasGroupId = false;
|
|
boolean hasArtifactId = false;
|
|
boolean hasVersion = false;
|
|
boolean hasScope = false;
|
|
for (Node node : iterable(doc.getChildNodes())) {
|
|
switch (node.getNodeName()) {
|
|
case "groupId" -> {
|
|
hasGroupId = true;
|
|
result.groupId = node.getTextContent();
|
|
}
|
|
case "artifactId" -> {
|
|
hasArtifactId = true;
|
|
result.artifactId = node.getTextContent();
|
|
}
|
|
case "version" -> {
|
|
hasVersion = true;
|
|
result.version = node.getTextContent();
|
|
}
|
|
case "scope" -> {
|
|
hasScope = true;
|
|
result.scope = node.getTextContent();
|
|
if (!RUNTIME_SCOPES.contains(result.scope)) return null;
|
|
}
|
|
case "optional" -> {
|
|
if (node.getTextContent().equals("true")) return null;
|
|
}
|
|
}
|
|
}
|
|
if (!hasGroupId) throw new IOException("Pom lacks groupId");
|
|
if (!hasArtifactId) throw new IOException("Pom lacks artifactId");
|
|
if (!hasVersion) {
|
|
if (result.groupId.equals("org.lwjgl")) {
|
|
// Lwjgl uses a shared bom for versions which I don't want to support
|
|
// The required modules are explicit dependencies of launcher-imgui anyway
|
|
return null;
|
|
}
|
|
throw new IOException("Dependency " + result.groupId + ":" + result.artifactId + " lacks version");
|
|
}
|
|
if (!hasScope) throw new IOException("Pom lacks scope");
|
|
return result;
|
|
}
|
|
|
|
private static boolean isWhitespace(Node node) {
|
|
if (node.getNodeType() == Node.TEXT_NODE && node.getTextContent().isBlank()) return true;
|
|
if (node.getNodeType() == Node.COMMENT_NODE) return true;
|
|
return false;
|
|
}
|
|
|
|
private static Iterable<Node> iterable(NodeList list) {
|
|
return () -> new Iterator<>() {
|
|
int index = 0;
|
|
@Override
|
|
public boolean hasNext() {
|
|
while (index < list.getLength() && isWhitespace(list.item(index))) {
|
|
index++;
|
|
}
|
|
return index < list.getLength();
|
|
}
|
|
|
|
@Override
|
|
public Node next() {
|
|
if (!hasNext()) throw new NoSuchElementException();
|
|
return list.item(index++);
|
|
}
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Converts an artifact in maven notation to a jar file path. The following are supported:
|
|
* - some.base.path:artifact:version -> some/base/path/artifact/version/artifact-version.jar
|
|
* - some.base.path:artifact:version:classifier -> some/base/path/artifact/version/artifact-version-classifier.jar
|
|
* @param mavenNotation An artifact in maven notation
|
|
* @return A file path
|
|
*/
|
|
public static String mavenNotationToJarPath(String mavenNotation) {
|
|
if (Objects.requireNonNull(mavenNotation).isEmpty()) throw new IllegalArgumentException("The notation is empty");
|
|
String[] lib = mavenNotation.split(":");
|
|
if (lib.length <= 1) throw new IllegalArgumentException("Not in maven notation");
|
|
if (lib.length == 2) throw new IllegalArgumentException("Skipping versions is not supported");
|
|
if (lib.length >= 5) throw new IllegalArgumentException("Unkown elements in maven notation");
|
|
String path = lib[0].replace('.', '/') + '/'; // Base
|
|
path += lib[1] + '/'; // Artifact name
|
|
path += lib[2] + '/'; // Version
|
|
if (lib.length == 3) { // artifact-version.jar
|
|
path += lib[1] + '-' + lib[2];
|
|
} else { // artifact-version-classifier.jar
|
|
path += lib[1] + '-' + lib[2] + "-" + lib[3];
|
|
}
|
|
return path + ".jar";
|
|
}
|
|
|
|
/**
|
|
* Converts an artifact in maven notation to a pom file path. The following are supported:
|
|
* - some.base.path:artifact:version -> some/base/path/artifact/version/artifact-version.pom
|
|
* - some.base.path:artifact:version:classifier -> some/base/path/artifact/version/artifact-version.pom
|
|
* @param mavenNotation An artifact in maven notation
|
|
* @return A file path
|
|
*/
|
|
public static String mavenNotationToPomPath(String mavenNotation) {
|
|
if (Objects.requireNonNull(mavenNotation).isEmpty()) throw new IllegalArgumentException("The notation is empty");
|
|
String[] lib = mavenNotation.split(":");
|
|
if (lib.length <= 1) throw new IllegalArgumentException("Not in maven notation");
|
|
if (lib.length == 2) throw new IllegalArgumentException("Skipping versions is not supported");
|
|
if (lib.length >= 5) throw new IllegalArgumentException("Unkown elements in maven notation");
|
|
String path = lib[0].replace('.', '/') + '/'; // Base
|
|
path += lib[1] + '/'; // Artifact name
|
|
path += lib[2] + '/'; // Version
|
|
path += lib[1] + '-' + lib[2]; // artifact-version
|
|
return path + ".pom";
|
|
}
|
|
}
|