package io.gitlab.jfronny.inceptum.common.api; import io.gitlab.jfronny.commons.HttpUtils; 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 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.localPath; 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 = HttpUtils.get(Utils.join("/", repo, meta.pomPath)).sendInputStream()) { Document doc = FACTORY.parse(is); doc.documentElement.normalize(); if (!"project".equals(doc.documentElement.nodeName)) throw new IOException("Illegal document name"); String modelVersion = null; String groupId = null; String artifactId = null; String version = null; String packaging = null; List dependencies = null; String classifier = null; for (Node node : doc.documentElement.childNodes) { switch (node.nodeName) { case "modelVersion" -> modelVersion = node.textContent; 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 : node.childNodes) { switch (child.nodeName) { case "groupId" -> { if (groupId == null) { groupId = child.textContent; } } case "version" -> { if (version == null) { version = child.textContent; } } } } } } case "groupId" -> groupId = node.textContent; case "artifactId" -> { artifactId = node.textContent; } case "version" -> { version = node.textContent; } case "packaging" -> packaging = node.textContent; case "dependencies" -> { dependencies = new LinkedList<>(); for (Node dep : node.childNodes) { MavenDependency resolved = parseDependency(dep); if (resolved != null) { dependencies.add(resolved); } } } case "classifier" -> classifier = node.textContent; 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 : doc.childNodes) { switch (node.nodeName) { case "groupId" -> groupId = node.textContent; case "artifactId" -> artifactId = node.textContent; case "version" -> version = node.textContent; case "scope" -> { scope = node.textContent; if (!RUNTIME_SCOPES.contains(scope)) return null; } case "optional" -> { if (node.textContent.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 = HttpUtils.get(Utils.join("/", repo, sourceMeta.metadataPath)).sendInputStream()) { Document doc = FACTORY.parse(is); doc.documentElement.normalize(); if (!"metadata".equals(doc.documentElement.nodeName)) throw new IOException("Illegal document name"); String groupId = null; String artifactId = null; String version = null; String snapshotVersion = null; for (Node node : doc.documentElement.childNodes) { switch (node.nodeName) { case "groupId" -> groupId = node.textContent; case "artifactId" -> artifactId = node.textContent; case "version" -> version = node.textContent; case "versioning" -> { for (Node node1 : node.childNodes) { if (node1.nodeName.equals("snapshot")) { String timestamp = null; String buildNumber = null; for (Node node2 : node1.childNodes) { switch (node2.nodeName) { case "timestamp" -> timestamp = node2.textContent; case "buildNumber" -> buildNumber = node2.textContent; 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); } } }