Inceptum/common/src/main/java/io/gitlab/jfronny/inceptum/common/api/MavenApi.java

203 lines
9.4 KiB
Java

package io.gitlab.jfronny.inceptum.common.api;
import io.gitlab.jfronny.commons.http.client.HttpClient;
import io.gitlab.jfronny.inceptum.common.Net;
import io.gitlab.jfronny.inceptum.common.Utils;
import io.gitlab.jfronny.inceptum.common.model.maven.*;
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, ArtifactMeta meta) throws IOException, URISyntaxException {
Path res = meta.getLocalPath();
Net.downloadFile(Utils.join("/", repo, meta.getJarPath(true)), res);
return res;
}
public static Pom getPom(String repo, ArtifactMeta meta) throws IOException, SAXException, URISyntaxException, XMLStreamException {
try (InputStream is = HttpClient.get(Utils.join("/", repo, meta.getPomPath())).sendInputStream()) {
Document doc = FACTORY.parse(is);
doc.getDocumentElement().normalize();
if (!"project".equals(doc.getDocumentElement().getNodeName())) throw new IOException("Illegal document name");
String modelVersion = null;
String groupId = null;
String artifactId = null;
String version = null;
String packaging = null;
List<MavenDependency> dependencies = null;
String classifier = null;
for (Node node : children(doc.getDocumentElement())) {
switch (node.getNodeName()) {
case "modelVersion" -> 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 (groupId == null) {
for (Node child : children(node)) {
switch (child.getNodeName()) {
case "groupId" -> {
if (groupId == null) {
groupId = child.getTextContent();
}
}
case "version" -> {
if (version == null) {
version = child.getTextContent();
}
}
}
}
}
}
case "groupId" -> groupId = node.getTextContent();
case "artifactId" -> {
artifactId = node.getTextContent();
}
case "version" -> {
version = node.getTextContent();
}
case "packaging" -> packaging = node.getTextContent();
case "dependencies" -> {
dependencies = new LinkedList<>();
for (Node dep : children(node)) {
MavenDependency resolved = parseDependency(dep);
if (resolved != null) {
dependencies.add(resolved);
}
}
}
case "classifier" -> classifier = node.getTextContent();
default -> {}
}
}
if (modelVersion == null) throw new IOException("Pom lacks modelVersion");
if (groupId == null) throw new IOException("Pom lacks groupId");
if (artifactId == null) throw new IOException("Pom lacks artifactId");
if (version == null) throw new IOException("Pom lacks version");
return new Pom(modelVersion, groupId, artifactId, version, classifier, null, packaging, dependencies);
}
}
private static @Nullable MavenDependency parseDependency(Node doc) throws IOException {
String groupId = null;
String artifactId = null;
String version = null;
String scope = null;
for (Node node : children(doc)) {
switch (node.getNodeName()) {
case "groupId" -> groupId = node.getTextContent();
case "artifactId" -> artifactId = node.getTextContent();
case "version" -> version = node.getTextContent();
case "scope" -> {
scope = node.getTextContent();
if (!RUNTIME_SCOPES.contains(scope)) return null;
}
case "optional" -> {
if (node.getTextContent().equals("true")) return null;
}
}
}
if (groupId == null) throw new IOException("Pom lacks groupId");
if (artifactId == null) throw new IOException("Pom lacks artifactId");
if (version == null) {
if (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 " + groupId + ":" + artifactId + " lacks version");
}
if (scope == null) throw new IOException("Pom lacks scope");
return new MavenDependency(groupId, artifactId, version, scope);
}
public static ArtifactMeta getMetadata(String repo, String artifact) throws IOException, SAXException, URISyntaxException {
ArtifactMeta sourceMeta = ArtifactMeta.parse(artifact);
try (InputStream is = HttpClient.get(Utils.join("/", repo, sourceMeta.getMetadataPath())).sendInputStream()) {
Document doc = FACTORY.parse(is);
doc.getDocumentElement().normalize();
if (!"metadata".equals(doc.getDocumentElement().getNodeName())) throw new IOException("Illegal document name");
String groupId = null;
String artifactId = null;
String version = null;
String snapshotVersion = null;
for (Node node : children(doc.getDocumentElement())) {
switch (node.getNodeName()) {
case "groupId" -> groupId = node.getTextContent();
case "artifactId" -> artifactId = node.getTextContent();
case "version" -> version = node.getTextContent();
case "versioning" -> {
for (Node node1 : children(node)) {
if (node1.getNodeName().equals("snapshot")) {
String timestamp = null;
String buildNumber = null;
for (Node node2 : children(node1)) {
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");
snapshotVersion = timestamp + '-' + buildNumber;
}
}
}
default -> {}
}
}
if (groupId == null) throw new IOException("Pom lacks groupId");
if (artifactId == null) throw new IOException("Pom lacks artifactId");
if (version == null) throw new IOException("Pom lacks version");
return new ArtifactMeta(groupId, artifactId, version, sourceMeta.classifier(), snapshotVersion);
}
}
private static Iterable<Node> children(Node node) {
return () -> new Iterator<Node>() {
NodeList children = node.getChildNodes();
int index = 0;
@Override
public boolean hasNext() {
while (index < children.getLength() && isWhitespace(children.item(index))) {
index++;
}
return index < children.getLength();
}
@Override
public Node next() {
if (!hasNext()) throw new NoSuchElementException();
return children.item(index++);
}
};
}
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;
}
}