1 /*
   2  * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package bldr;
  27 
  28 import com.sun.source.util.JavacTask;
  29 import org.w3c.dom.Attr;
  30 import org.w3c.dom.Document;
  31 import org.w3c.dom.Element;
  32 import org.w3c.dom.Node;
  33 import org.w3c.dom.NodeList;
  34 import org.xml.sax.SAXException;
  35 
  36 import javax.tools.Diagnostic;
  37 import javax.tools.DiagnosticListener;
  38 import javax.tools.JavaCompiler;
  39 import javax.tools.JavaFileObject;
  40 import javax.tools.SimpleJavaFileObject;
  41 import javax.tools.ToolProvider;
  42 import javax.xml.parsers.DocumentBuilderFactory;
  43 import javax.xml.parsers.ParserConfigurationException;
  44 import javax.xml.transform.OutputKeys;
  45 import javax.xml.transform.TransformerFactory;
  46 import javax.xml.transform.dom.DOMSource;
  47 import javax.xml.transform.stream.StreamResult;
  48 import javax.xml.xpath.XPath;
  49 import javax.xml.xpath.XPathConstants;
  50 import javax.xml.xpath.XPathExpression;
  51 import javax.xml.xpath.XPathExpressionException;
  52 import javax.xml.xpath.XPathFactory;
  53 import java.io.BufferedReader;
  54 import java.io.File;
  55 import java.io.IOException;
  56 import java.io.InputStream;
  57 import java.io.InputStreamReader;
  58 import java.io.PrintWriter;
  59 import java.io.StringWriter;
  60 import java.net.MalformedURLException;
  61 import java.net.URI;
  62 import java.net.URISyntaxException;
  63 import java.net.URL;
  64 import java.net.URLEncoder;
  65 import java.nio.charset.StandardCharsets;
  66 import java.nio.file.Files;
  67 import java.nio.file.Path;
  68 import java.nio.file.StandardOpenOption;
  69 import java.util.ArrayList;
  70 import java.util.Arrays;
  71 import java.util.Comparator;
  72 import java.util.HashMap;
  73 import java.util.HashSet;
  74 import java.util.List;
  75 import java.util.Map;
  76 import java.util.Objects;
  77 import java.util.Optional;
  78 import java.util.Set;
  79 import java.util.function.BiConsumer;
  80 import java.util.function.Consumer;
  81 import java.util.function.Function;
  82 import java.util.function.Predicate;
  83 import java.util.jar.JarEntry;
  84 import java.util.jar.JarOutputStream;
  85 import java.util.regex.Matcher;
  86 import java.util.regex.Pattern;
  87 import java.util.stream.Stream;
  88 import java.util.zip.ZipFile;
  89 
  90 import static java.io.IO.print;
  91 import static java.io.IO.println;
  92 
  93 public class Bldr {
  94     public sealed interface PathHolder permits ClassPathEntry, DirPathHolder, FilePathHolder, SourcePathEntry {
  95         default Path path(String subdir) {
  96             return path().resolve(subdir);
  97         }
  98 
  99         default String fileName() {
 100             return path().getFileName().toString();
 101         }
 102 
 103         default Matcher pathMatcher(Pattern pattern) {
 104             return pattern.matcher(path().toString());
 105         }
 106 
 107         default boolean matches(Pattern pattern) {
 108             return pathMatcher(pattern).matches();
 109         }
 110 
 111         default boolean matches(String pattern) {
 112             return pathMatcher(Pattern.compile(pattern)).matches();
 113         }
 114         default boolean failsToMatch(String pattern) {
 115             return !pathMatcher(Pattern.compile(pattern)).matches();
 116         }
 117 
 118         boolean exists();
 119 
 120         Path path();
 121     }
 122 
 123     public sealed interface DirPathHolder<T extends DirPathHolder<T>> extends PathHolder
 124             permits BuildDirHolder, DirEntry, SourceDir {
 125 
 126         default Stream<Path> find() {
 127             try {
 128                 return Files.walk(path());
 129             } catch (IOException e) {
 130                 throw new RuntimeException(e);
 131             }
 132         }
 133 
 134         default Stream<Path> find(Predicate<Path> predicate) {
 135             return find().filter(predicate);
 136         }
 137 
 138         default Stream<Path> findFiles() {
 139             return find(Files::isRegularFile);
 140         }
 141 
 142         default Stream<Path> findDirs() {
 143             return find(Files::isDirectory);
 144         }
 145 
 146         default Stream<Path> findFiles(Predicate<Path> predicate) {
 147             return findFiles().filter(predicate);
 148         }
 149 
 150         default Stream<Path> findFilesBySuffix(String suffix) {
 151             return findFiles(p -> p.toString().endsWith(suffix));
 152         }
 153 
 154         default Stream<SearchableTextFile> findTextFiles(String... suffixes) {
 155             return findFiles()
 156                     .map(SearchableTextFile::new)
 157                     .filter(searchableTextFile -> searchableTextFile.hasSuffix(suffixes));
 158         }
 159 
 160         default Stream<Path> findDirs(Predicate<Path> predicate) {
 161             return find(Files::isDirectory).filter(predicate);
 162         }
 163 
 164         default boolean exists() {
 165             return Files.isDirectory(path());
 166         }
 167 
 168         default BuildDir buildDir(String name) {
 169             return BuildDir.of(path().resolve(name));
 170         }
 171 
 172         default SourceDir sourceDir(String s) {
 173             return SourceDir.of(path().resolve(s));
 174         }
 175 
 176         default CppSourceFile cppSourceFile(String s) {
 177             return CppSourceFile.of(path().resolve(s));
 178         }
 179 
 180         default XMLFile xmlFile(String s) {
 181             return XMLFile.of(path().resolve(s));
 182         }
 183 
 184         default TestNGSuiteFile testNGSuiteFile(String s) {
 185             return TestNGSuiteFile.of(path().resolve(s));
 186         }
 187     }
 188 
 189     public sealed interface FilePathHolder extends PathHolder {
 190         default boolean exists() {
 191             return Files.isRegularFile(path());
 192         }
 193     }
 194 
 195     public sealed interface Executable extends FilePathHolder {
 196         default boolean exists() {
 197             return Files.exists(path()) && Files.isRegularFile(path()) && Files.isExecutable(path());
 198         }
 199     }
 200 
 201 
 202     public interface ClassPathEntryProvider {
 203         List<ClassPathEntry> classPathEntries();
 204     }
 205 
 206     public sealed interface ClassPathEntry extends PathHolder, ClassPathEntryProvider {
 207     }
 208 
 209     interface PathHolderList<T extends PathHolder> {
 210         List<T> entries();
 211 
 212         default String charSeparated() {
 213             StringBuilder sb = new StringBuilder();
 214             entries().forEach(pathHolder -> {
 215                 if (!sb.isEmpty()) {
 216                     sb.append(File.pathSeparatorChar);
 217                 }
 218                 sb.append(pathHolder.path());
 219             });
 220             return sb.toString();
 221         }
 222     }
 223 
 224     public record ClassPath(List<ClassPathEntry> classPathEntries)
 225             implements PathHolderList<ClassPathEntry>, ClassPathEntryProvider {
 226         public static ClassPath of() {
 227             return new ClassPath(new ArrayList<>());
 228         }
 229 
 230         public static ClassPath ofOrUse(ClassPath classPath) {
 231             return classPath == null ? of() : classPath;
 232         }
 233 
 234         public ClassPath add(List<ClassPathEntryProvider> classPathEntryProviders) {
 235             classPathEntryProviders.forEach(
 236                     classPathEntryProvider ->
 237                             this.classPathEntries.addAll(classPathEntryProvider.classPathEntries()));
 238             return this;
 239         }
 240 
 241         public ClassPath add(ClassPathEntryProvider... classPathEntryProviders) {
 242             return add(List.of(classPathEntryProviders));
 243         }
 244 
 245         @Override
 246         public String toString() {
 247             return charSeparated();
 248         }
 249 
 250         @Override
 251         public List<ClassPathEntry> classPathEntries() {
 252             return this.classPathEntries;
 253         }
 254 
 255         @Override
 256         public List<ClassPathEntry> entries() {
 257             return this.classPathEntries;
 258         }
 259     }
 260 
 261     public record SourcePath(List<SourceDir> entries)
 262             implements PathHolderList<SourceDir> {
 263         public static SourcePath of() {
 264             return new SourcePath(new ArrayList<>());
 265         }
 266 
 267         public static SourcePath ofOrUse(SourcePath sourcePath) {
 268             return sourcePath == null ? of() : sourcePath;
 269         }
 270 
 271         public SourcePath add(List<SourceDir> sourcePathEntries) {
 272             entries.addAll(sourcePathEntries);
 273             return this;
 274         }
 275 
 276         public SourcePath add(SourceDir... sourcePathEntries) {
 277             add(Arrays.asList(sourcePathEntries));
 278             return this;
 279         }
 280 
 281         public SourcePath add(SourcePath... sourcePaths) {
 282             List.of(sourcePaths).forEach(sourcePath -> add(sourcePath.entries));
 283             return this;
 284         }
 285 
 286         @Override
 287         public String toString() {
 288             return charSeparated();
 289         }
 290 
 291         public Stream<Path> javaFiles() {
 292             List<Path> javaFiles = new ArrayList<>();
 293             entries.forEach(entry -> entry.javaFiles().forEach(javaFiles::add));
 294             return javaFiles.stream();
 295         }
 296     }
 297 
 298     public record DirPath(List<DirPathHolder<?>> entries)
 299             implements PathHolderList<DirPathHolder<?>> {
 300         public static DirPath of() {
 301             return new DirPath(new ArrayList<>());
 302         }
 303 
 304         public static DirPath ofOrUse(DirPath dirPath) {
 305             return dirPath == null ? of() : dirPath;
 306         }
 307 
 308         public DirPath add(List<DirPathHolder<?>> dirPathHolders) {
 309             entries.addAll(dirPathHolders);
 310             return this;
 311         }
 312 
 313         public DirPath add(DirPathHolder<?>... dirPathHolders) {
 314             add(Arrays.asList(dirPathHolders));
 315             return this;
 316         }
 317 
 318         public DirPath add(DirPath... dirPaths) {
 319             List.of(dirPaths).forEach(dirPath -> add(dirPath.entries));
 320             return this;
 321         }
 322 
 323         @Override
 324         public String toString() {
 325             return charSeparated();
 326         }
 327     }
 328 
 329     public record CMakeBuildDir(Path path) implements BuildDirHolder<CMakeBuildDir> {
 330         public static CMakeBuildDir of(Path path) {
 331             return new CMakeBuildDir(path);
 332         }
 333 
 334         @Override
 335         public CMakeBuildDir create() {
 336             return CMakeBuildDir.of(mkdir(path()));
 337         }
 338 
 339         @Override
 340         public CMakeBuildDir remove() {
 341             return CMakeBuildDir.of(rmdir(path()));
 342         }
 343     }
 344 
 345     public sealed interface BuildDirHolder<T extends BuildDirHolder<T>> extends DirPathHolder<T> {
 346         T create();
 347 
 348         T remove();
 349 
 350         default T clean() {
 351             remove();
 352             return create();
 353         }
 354 
 355         default Path mkdir(Path path) {
 356             try {
 357                 return Files.createDirectories(path);
 358             } catch (IOException e) {
 359                 throw new RuntimeException(e);
 360             }
 361         }
 362 
 363         default Path rmdir(Path path) {
 364             try {
 365                 if (Files.exists(path)) {
 366                     Files.walk(path)
 367                             .sorted(Comparator.reverseOrder())
 368                             .map(Path::toFile)
 369                             .forEach(File::delete);
 370                 }
 371             } catch (IOException ioe) {
 372                 System.out.println(ioe);
 373             }
 374             return path;
 375         }
 376     }
 377 
 378 
 379     public record ClassDir(Path path) implements ClassPathEntry, BuildDirHolder<ClassDir> {
 380         public static ClassDir of(Path path) {
 381             return new ClassDir(path);
 382         }
 383 
 384         public static ClassDir temp() {
 385             try {
 386                 return of(Files.createTempDirectory("javacClasses"));
 387             } catch (IOException e) {
 388                 throw new RuntimeException(e);
 389             }
 390         }
 391 
 392         @Override
 393         public ClassDir create() {
 394             return ClassDir.of(mkdir(path()));
 395         }
 396 
 397         @Override
 398         public ClassDir remove() {
 399             return ClassDir.of(rmdir(path()));
 400         }
 401 
 402         @Override
 403         public List<ClassPathEntry> classPathEntries() {
 404             return List.of(this);
 405         }
 406     }
 407 
 408     public record RepoDir(Path path) implements BuildDirHolder<RepoDir> {
 409         public static RepoDir of(Path path) {
 410             return new RepoDir(path);
 411         }
 412 
 413         @Override
 414         public RepoDir create() {
 415             return RepoDir.of(mkdir(path()));
 416         }
 417 
 418         @Override
 419         public RepoDir remove() {
 420             return RepoDir.of(rmdir(path()));
 421         }
 422 
 423         public JarFile jarFile(String name) {
 424             return JarFile.of(path().resolve(name));
 425         }
 426 
 427         public ClassPathEntryProvider classPathEntries(String... specs) {
 428             var repo = new MavenStyleRepository(this);
 429             return repo.classPathEntries(specs);
 430         }
 431     }
 432 
 433     public record DirEntry(Path path) implements DirPathHolder<DirEntry> {
 434         public static DirEntry of(Path path) {
 435             return new DirEntry(path);
 436         }
 437 
 438         public static DirEntry of(String string) {
 439             return of(Path.of(string));
 440         }
 441 
 442         public static DirEntry ofExisting(String string) {
 443             return of(assertExists(Path.of(string)));
 444         }
 445 
 446         public static DirEntry current() {
 447             return of(Path.of(System.getProperty("user.dir")));
 448         }
 449 
 450         public DirEntry parent() {
 451             return of(path().getParent());
 452         }
 453 
 454         public DirEntry dir(String subdir) {
 455             return DirEntry.of(path(subdir));
 456         }
 457 
 458         public FileEntry file(String fileName) {
 459             return FileEntry.of(path(fileName));
 460         }
 461 
 462         public DirEntry existingDir(String subdir) {
 463             return assertExists(DirEntry.of(path(subdir)));
 464         }
 465 
 466         public Stream<DirEntry> subDirs() {
 467             return Stream.of(Objects.requireNonNull(path().toFile().listFiles(File::isDirectory)))
 468                     .map(d -> DirEntry.of(d.getPath()));
 469         }
 470 
 471        // public Stream<DirEntry> subDirs(Predicate<DirEntry> predicate) {
 472         //    return subDirs().filter(predicate);
 473        // }
 474 
 475         public XMLFile pom(
 476                 String comment, Consumer<XMLNode.PomXmlBuilder> pomXmlBuilderConsumer) {
 477             XMLFile xmlFile = xmlFile("pom.xml");
 478             XMLNode.createPom(comment, pomXmlBuilderConsumer).write(xmlFile);
 479             return xmlFile;
 480         }
 481 
 482         public BuildDir existingBuildDir(String subdir) {
 483             return assertExists(BuildDir.of(path(subdir)));
 484         }
 485     }
 486 
 487     public interface SourcePathEntryProvider {
 488         List<SourcePathEntry> sourcePathEntries();
 489     }
 490 
 491     public sealed interface SourcePathEntry extends PathHolder, SourcePathEntryProvider {
 492     }
 493 
 494     public record SourceDir(Path path) implements SourcePathEntry, DirPathHolder<SourceDir> {
 495         public static SourceDir of(Path path) {
 496             return new SourceDir(path);
 497         }
 498 
 499         public Stream<Path> javaFiles() {
 500             return findFilesBySuffix(".java");
 501         }
 502 
 503         @Override
 504         public List<SourcePathEntry> sourcePathEntries() {
 505             return List.of(this);
 506         }
 507     }
 508 
 509     public record RootDirAndSubPath(DirPathHolder<?> root, Path path) {
 510         Path relativize() {
 511             return root().path().relativize(path());
 512         }
 513     }
 514 
 515     public record BuildDir(Path path) implements ClassPathEntry, BuildDirHolder<BuildDir> {
 516         public static BuildDir of(Path path) {
 517             return new BuildDir(path);
 518         }
 519 
 520         public JarFile jarFile(String name) {
 521             return JarFile.of(path().resolve(name));
 522         }
 523 
 524         public ClassPathEntryProvider jarFiles(String... names) {
 525             var classPath = ClassPath.of();
 526             Stream.of(names).forEach(name -> classPath.add(JarFile.of(path().resolve(name))));
 527             return classPath;
 528         }
 529 
 530 
 531         public CMakeBuildDir cMakeBuildDir(String name) {
 532             return CMakeBuildDir.of(path().resolve(name));
 533         }
 534 
 535         public ClassDir classDir(String name) {
 536             return ClassDir.of(path().resolve(name));
 537         }
 538 
 539         public RepoDir repoDir(String name) {
 540             return RepoDir.of(path().resolve(name));
 541         }
 542 
 543         @Override
 544         public BuildDir create() {
 545             return BuildDir.of(mkdir(path()));
 546         }
 547 
 548 
 549         @Override
 550         public BuildDir remove() {
 551             return BuildDir.of(rmdir(path()));
 552         }
 553 
 554         public BuildDir dir(String subdir) {
 555             return BuildDir.of(path(subdir));
 556         }
 557 
 558         public ObjectFile objectFile(String name) {
 559             return ObjectFile.of(path().resolve(name));
 560         }
 561 
 562         public ExecutableFile executableFile(String name) {
 563             return ExecutableFile.of(path().resolve(name));
 564         }
 565 
 566         public SharedLibraryFile sharedLibraryFile(String name) {
 567             return SharedLibraryFile.of(path().resolve(name));
 568         }
 569 
 570         @Override
 571         public List<ClassPathEntry> classPathEntries() {
 572             return List.of(this);
 573         }
 574 
 575         public SearchableTextFile textFile(String file, List<String> strings) {
 576             SearchableTextFile textFile = SearchableTextFile.of(path().resolve(file));
 577             try {
 578                 PrintWriter pw = new PrintWriter(Files.newOutputStream(textFile.path(), StandardOpenOption.CREATE));
 579                 strings.forEach(pw::print);
 580                 pw.close();
 581                 return textFile;
 582             } catch (IOException e) {
 583                 throw new RuntimeException(e);
 584             }
 585         }
 586 
 587         public SearchableTextFile textFile(String file, Consumer<StringBuilder> stringBuilderConsumer) {
 588             SearchableTextFile textFile = SearchableTextFile.of(path().resolve(file));
 589             var sb = new StringBuilder();
 590             stringBuilderConsumer.accept(sb);
 591             try {
 592                 Files.writeString(textFile.path, sb.toString());
 593                 return textFile;
 594             } catch (IOException e) {
 595                 throw new RuntimeException(e);
 596             }
 597         }
 598 
 599 
 600         public CMakeLists cmakeLists(Consumer<StringBuilder> stringBuilderConsumer) {
 601             var sb = new StringBuilder();
 602             stringBuilderConsumer.accept(sb);
 603             var ret = CMakeLists.of(path().resolve("CMakeLists.txt"));
 604             try {
 605                 Files.writeString(ret.path, sb.toString());
 606                 return ret;
 607             } catch (IOException e) {
 608                 throw new RuntimeException(e);
 609             }
 610         }
 611     }
 612 
 613     public record FileEntry(Path path) implements FilePathHolder {
 614         public static FileEntry of(Path path) {
 615             return new FileEntry(path);
 616         }
 617     }
 618 
 619     public record JarFile(Path path) implements ClassPathEntry, FilePathHolder {
 620         public static JarFile of(Path path) {
 621             return new JarFile(path);
 622         }
 623 
 624         @Override
 625         public List<ClassPathEntry> classPathEntries() {
 626             return List.of(this);
 627         }
 628     }
 629 
 630     public sealed interface TextFile extends FilePathHolder {
 631 
 632         static Path tempContaining(String suffix, String text) {
 633             try {
 634                 var path = Files.createTempFile(Files.createTempDirectory("bldr"), "data", suffix);
 635                 Files.newOutputStream(path).write(text.getBytes());
 636                 return path;
 637             } catch (IOException e) {
 638                 throw new RuntimeException(e);
 639             }
 640         }
 641     }
 642 
 643 
 644     public sealed interface SourceFile extends TextFile {
 645     }
 646 
 647     public static final class CMakeLists implements SourceFile {
 648         Path path;
 649 
 650         CMakeLists(Path path) {
 651             this.path = path;
 652         }
 653 
 654         public static CMakeLists of(Path path) {
 655             return new CMakeLists(path);
 656         }
 657 
 658         @Override
 659         public Path path() {
 660             return path;
 661         }
 662     }
 663 
 664     public static final class JavaSourceFile extends SimpleJavaFileObject implements SourceFile {
 665         Path path;
 666 
 667         public CharSequence getCharContent(boolean ignoreEncodingErrors) {
 668             try {
 669                 return Files.readString(Path.of(toUri()));
 670             } catch (IOException e) {
 671                 throw new RuntimeException(e);
 672             }
 673         }
 674 
 675         JavaSourceFile(Path path) {
 676             super(path.toUri(), Kind.SOURCE);
 677             this.path = path;
 678         }
 679 
 680         @Override
 681         public Path path() {
 682             return path;
 683         }
 684     }
 685 
 686     public record JExtractExecutable(Path path) implements Executable {
 687         public static JExtractExecutable of(Path path) {
 688             return new JExtractExecutable(path);
 689         }
 690     }
 691 
 692 
 693     public record ObjectFile(Path path) implements FilePathHolder {
 694         public static ObjectFile of(Path path) {
 695             return new ObjectFile(path);
 696         }
 697     }
 698 
 699     public record ExecutableFile(Path path) implements FilePathHolder {
 700         public static ExecutableFile of(Path path) {
 701             return new ExecutableFile(path);
 702         }
 703     }
 704 
 705     public record SharedLibraryFile(Path path) implements FilePathHolder {
 706         public static SharedLibraryFile of(Path path) {
 707             return new SharedLibraryFile(path);
 708         }
 709     }
 710 
 711     public record CppSourceFile(Path path) implements SourceFile {
 712         public static CppSourceFile of(Path path) {
 713             return new CppSourceFile(path);
 714         }
 715     }
 716 
 717     public record CppHeaderSourceFile(Path path) implements SourceFile {
 718     }
 719 
 720     public record XMLFile(Path path) implements TextFile {
 721         public static XMLFile of(Path path) {
 722             return new XMLFile(path);
 723         }
 724 
 725         public static XMLFile containing(String text) {
 726             return XMLFile.of(TextFile.tempContaining("xml", text));
 727         }
 728     }
 729 
 730     public record TestNGSuiteFile(Path path) implements TextFile {
 731         public static TestNGSuiteFile of(Path path) {
 732             return new TestNGSuiteFile(path);
 733         }
 734 
 735         public static TestNGSuiteFile containing(String text) {
 736             return TestNGSuiteFile.of(TextFile.tempContaining("xml", text));
 737         }
 738     }
 739 
 740     public interface OS {
 741         String arch();
 742 
 743         String name();
 744 
 745         String version();
 746 
 747         String MacName = "Mac OS X";
 748         String LinuxName = "Linux";
 749 
 750         record Linux(String arch, String name, String version) implements OS {
 751         }
 752 
 753         record Mac(String arch, String name, String version) implements OS {
 754             public Path appLibFrameworks() {
 755                 return Path.of(
 756                         "/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/"
 757                                 + "MacOSX.sdk/System/Library/Frameworks");
 758             }
 759 
 760             public Path frameworkHeader(String frameworkName, String headerFileName) {
 761                 return appLibFrameworks().resolve(frameworkName + ".framework/Headers/" + headerFileName);
 762             }
 763 
 764             public Path libFrameworks() {
 765                 return Path.of("/System/Library/Frameworks");
 766             }
 767 
 768             public Path frameworkLibrary(String frameworkName) {
 769                 return libFrameworks().resolve(frameworkName + ".framework/" + frameworkName);
 770             }
 771         }
 772 
 773         static OS get() {
 774             String arch = System.getProperty("os.arch");
 775             String name = System.getProperty("os.name");
 776             String version = System.getProperty("os.version");
 777             return switch (name) {
 778                 case "Mac OS X" -> new Mac(arch, name, version);
 779                 case "Linux" -> new Linux(arch, name, version);
 780                 default -> throw new IllegalStateException("No os mapping for " + name);
 781             };
 782         }
 783     }
 784 
 785     public static OS os = OS.get();
 786 
 787     public record Java(String version, DirEntry home, int specVersion) {
 788     }
 789 
 790     public static Java java =
 791             new Java(
 792                     System.getProperty("java.version"),
 793                     DirEntry.of(System.getProperty("java.home")),
 794                     Integer.parseInt(System.getProperty("java.specification.version"))
 795             );
 796 
 797     public record User(DirEntry home, DirEntry pwd) {
 798     }
 799 
 800     public static User user =
 801             new User(DirEntry.of(System.getProperty("user.home")), DirEntry.of(System.getProperty("user.dir")));
 802 
 803 
 804     public abstract sealed static class Builder<T extends Builder<T>> permits CMakeBuilder, FormatBuilder, JExtractBuilder, JarBuilder, JarBuilder.ManifestBuilder, JavaOpts, TestNGBuilder {
 805         public Builder<?> parent;
 806         public boolean verbose;
 807         public boolean quiet;
 808 
 809         @SuppressWarnings("unchecked")
 810         T self() {
 811             return (T) this;
 812         }
 813 
 814         protected T dontCallThisCopy(T other) {
 815             this.verbose = other.verbose;
 816             this.quiet = other.quiet;
 817             return self();
 818         }
 819 
 820         public T quiet(boolean quiet) {
 821             this.quiet = quiet;
 822             return self();
 823         }
 824 
 825         public T quiet() {
 826             quiet(true);
 827             return self();
 828         }
 829 
 830         public T verbose(boolean verbose) {
 831             this.verbose = verbose;
 832             return self();
 833         }
 834 
 835         public T verbose() {
 836             verbose(true);
 837             return self();
 838         }
 839 
 840         public T when(boolean condition, Consumer<T> consumer) {
 841             if (condition) {
 842                 consumer.accept(self());
 843             }
 844             return self();
 845         }
 846 
 847         public <P extends PathHolder> T whenExists(P pathHolder, Consumer<T> consumer) {
 848             if (Files.exists(pathHolder.path())) {
 849                 consumer.accept(self());
 850             }
 851             return self();
 852         }
 853 
 854         public <P extends PathHolder> T whenExists(P pathHolder, BiConsumer<P, T> consumer) {
 855             if (Files.exists(pathHolder.path())) {
 856                 consumer.accept(pathHolder, self());
 857             }
 858             return self();
 859         }
 860 
 861         public T either(boolean condition, Consumer<T> trueConsumer, Consumer<T> falseConsumer) {
 862             if (condition) {
 863                 trueConsumer.accept(self());
 864             } else {
 865                 falseConsumer.accept(self());
 866             }
 867             return self();
 868         }
 869 
 870         Builder(Builder<?> parent) {
 871             this.parent = parent;
 872         }
 873 
 874         Builder() {
 875             this(null);
 876         }
 877 
 878         public T mac(Consumer<OS.Mac> macConsumer) {
 879             if (Bldr.os instanceof OS.Mac mac) {
 880                 macConsumer.accept(mac);
 881             }
 882             return self();
 883         }
 884 
 885         public T linux(Consumer<OS.Linux> linuxConsumer) {
 886             if (Bldr.os instanceof OS.Linux linux) {
 887                 linuxConsumer.accept(linux);
 888             }
 889             return self();
 890         }
 891 
 892         public T os(Consumer<OS.Mac> macConsumer, Consumer<OS.Linux> linuxConsumer) {
 893             switch (Bldr.os) {
 894                 case OS.Linux linux -> linuxConsumer.accept(linux);
 895                 case OS.Mac mac -> macConsumer.accept(mac);
 896                 default -> throw new IllegalStateException("Unexpected value: " + Bldr.os);
 897             }
 898             ;
 899             return self();
 900         }
 901     }
 902 
 903     public abstract static sealed class Result<T extends Builder<T>> permits JExtractResult, JarResult, JavaResult, JavacResult {
 904         public boolean ok;
 905         public T builder;
 906 
 907         Result(T builder) {
 908             this.builder = builder;
 909         }
 910     }
 911 
 912     public static class Strings {
 913         public List<String> strings = new ArrayList<>();
 914 
 915         Strings() {
 916         }
 917 
 918         Strings(Strings strings) {
 919             add(strings);
 920         }
 921 
 922         Strings(List<String> strings) {
 923             add(strings);
 924         }
 925 
 926         Strings(String... strings) {
 927             add(strings);
 928         }
 929 
 930         public Strings add(List<String> strings) {
 931             this.strings.addAll(strings);
 932             return this;
 933         }
 934 
 935         public Strings add(String... strings) {
 936             add(Arrays.asList(strings));
 937             return this;
 938         }
 939 
 940         public Strings add(Strings strings) {
 941             add(strings.strings);
 942             return this;
 943         }
 944 
 945         public String spaceSeparated() {
 946             StringBuilder stringBuilder = new StringBuilder();
 947             strings.forEach(opt -> stringBuilder.append(stringBuilder.isEmpty() ? "" : " ").append(opt));
 948             return stringBuilder.toString();
 949         }
 950     }
 951 
 952 
 953     public static sealed class JavaOpts<T extends JavaOpts<T>> extends Builder<T> {
 954         public DirEntry jdk = java.home;
 955         public Boolean enablePreview;
 956         public Strings modules;
 957 
 958         record FromModulePackageToModule(String fromModule, String pkg, String toModule) {
 959         }
 960 
 961         List<FromModulePackageToModule> exports;
 962 
 963         protected T dontCallThisCopy(T other) {
 964             super.dontCallThisCopy(other);
 965             if (other.jdk != null) {
 966                 this.jdk = other.jdk;
 967             }
 968             if (other.enablePreview != null) {
 969                 this.enablePreview = other.enablePreview;
 970             }
 971             if (other.modules != null) {
 972                 this.modules = new Strings(other.modules);
 973             }
 974             if (other.exports != null) {
 975                 this.exports = new ArrayList<>(other.exports);
 976             }
 977 
 978             return self();
 979         }
 980 
 981         public JavaOpts(Builder<?> parent) {
 982             super(parent);
 983         }
 984 
 985         public JavaOpts() {
 986             super();
 987         }
 988 
 989         static public JavaOpts<?> of() {
 990             return new JavaOpts<>();
 991         }
 992 
 993         public T jdk(DirEntry jdk) {
 994             this.jdk = jdk;
 995             return self();
 996         }
 997 
 998         public T add_exports(String fromModule, String pkg, String toModule) {
 999             if (this.exports == null) {
1000                 this.exports = new ArrayList<>();
1001             }
1002             exports.add(new FromModulePackageToModule(fromModule, pkg, toModule));
1003             return self();
1004         }
1005 
1006         public T add_modules(String... modules) {
1007             if (this.modules == null) {
1008                 this.modules = new Strings();
1009             }
1010             this.modules.add(modules);
1011 
1012             return self();
1013         }
1014 
1015         public T add_exports(String fromModule, List<String> packages, String toModule) {
1016 
1017             packages.forEach(p -> add_exports(fromModule, p, toModule));
1018             return self();
1019         }
1020 
1021         public T add_exports(String fromModule, String[] packages, String toModule) {
1022             return add_exports(fromModule, Arrays.asList(packages), toModule);
1023         }
1024 
1025         public T add_exports_to_all_unnamed(String fromModule, String... packages) {
1026             return add_exports(fromModule, Arrays.asList(packages), "ALL-UNNAMED");
1027         }
1028 
1029         public T enable_preview() {
1030             this.enablePreview = true;
1031             return self();
1032         }
1033 
1034 
1035     }
1036 
1037     public abstract sealed static class JavaToolBuilder<T extends JavaToolBuilder<T>> extends JavaOpts<T> permits JavacBuilder, JavaBuilder {
1038         public ClassPath classPath;
1039 
1040         protected T dontCallThisCopy(T other) {
1041             super.dontCallThisCopy(other);
1042             if (other.classPath != null) {
1043                 this.classPath = ClassPath.of().add(other.classPath);
1044             }
1045             return self();
1046         }
1047 
1048         public JavaToolBuilder(Builder<?> parent) {
1049             super(parent);
1050         }
1051 
1052         public JavaToolBuilder() {
1053             super();
1054         }
1055 
1056         public T class_path(List<ClassPathEntryProvider> classPathEntryProviders) {
1057             this.classPath = ClassPath.ofOrUse(this.classPath).add(classPathEntryProviders);
1058             return self();
1059         }
1060 
1061         public T class_path(ClassPathEntryProvider... classPathEntryProviders) {
1062             return class_path(List.of(classPathEntryProviders));
1063         }
1064     }
1065 
1066     public static final class JavacBuilder extends JavaToolBuilder<JavacBuilder> {
1067         public DirEntry mavenStyleRoot;
1068         public ClassDir classDir;
1069         public SourcePath sourcePath;
1070         public ClassPath modulePath;
1071         public SourcePath moduleSourcePath;
1072         public Integer source;
1073         public List<Predicate<JavaSourceFile>> exclusionFilters;
1074 
1075         protected JavacBuilder dontCallThisCopy(JavacBuilder other) {
1076             super.dontCallThisCopy(other);
1077             if (other.mavenStyleRoot != null) {
1078                 throw new RuntimeException("You are copying a JavacBuilder which is already bound to maven style dir");
1079             }
1080             if (other.sourcePath != null) {
1081                 throw new RuntimeException("You are copying a JavacBuilder which is already bound to a SourcePath");
1082             }
1083             if (other.moduleSourcePath != null) {
1084                 throw new RuntimeException("You are copying a JavacBuilder which is already bound to a ModuleSourcePath");
1085             }
1086 
1087             if (other.source != null) {
1088                 this.source = other.source;
1089             }
1090 
1091             if (other.classPath != null) {
1092                 ClassPath.ofOrUse(this.classPath).add(other.classPath);
1093             }
1094             return this;
1095         }
1096 
1097         public JavacBuilder source(int version) {
1098             this.source = version;
1099             return self();
1100         }
1101 
1102         public JavacBuilder current_source() {
1103             return source(Bldr.java.specVersion);
1104         }
1105 
1106         public JavacBuilder maven_style_root(DirEntry mavenStyleRoot) {
1107             this.mavenStyleRoot = mavenStyleRoot;
1108             return this;
1109         }
1110 
1111         public JavacBuilder class_dir(Path classDir) {
1112             this.classDir = ClassDir.of(classDir);
1113             return this;
1114         }
1115 
1116         public JavacBuilder class_dir(ClassDir classDir) {
1117             this.classDir = classDir;
1118             return this;
1119         }
1120 
1121         public JavacBuilder d(ClassDir classDir) {
1122             this.classDir = classDir;
1123             return this;
1124         }
1125 
1126         public JavacBuilder source_path(List<SourceDir> sourcePaths) {
1127             this.sourcePath = SourcePath.ofOrUse(this.sourcePath).add(sourcePaths);
1128             return this;
1129         }
1130 
1131         public JavacBuilder source_path(SourceDir... sourcePathEntries) {
1132             return source_path(List.of(sourcePathEntries));
1133         }
1134 
1135         public JavacBuilder source_path(SourcePath sourcePath) {
1136             return source_path(sourcePath.entries);
1137         }
1138 
1139         public JavacBuilder module_source_path(List<SourceDir> moduleSourcePathEntries) {
1140             this.moduleSourcePath = SourcePath.ofOrUse(this.moduleSourcePath).add(moduleSourcePathEntries);
1141             return this;
1142         }
1143 
1144         public JavacBuilder module_source_path(SourceDir... moduleSourcePathEntries) {
1145             return module_source_path(List.of(moduleSourcePathEntries));
1146         }
1147 
1148         public JavacBuilder module_source_path(SourcePath moduleSourcePath) {
1149             return module_source_path(moduleSourcePath.entries());
1150         }
1151 
1152         public JavacBuilder() {
1153             super();
1154         }
1155 
1156         public JavacBuilder(JarBuilder jarBuilder) {
1157             super(jarBuilder);
1158         }
1159 
1160         public JavacBuilder exclude(Predicate<JavaSourceFile> javaSourceFileFilter) {
1161             this.exclusionFilters = (this.exclusionFilters == null ? new ArrayList<>() : this.exclusionFilters);
1162             this.exclusionFilters.add(javaSourceFileFilter);
1163             return self();
1164         }
1165     }
1166 
1167     public static final class JavacResult extends Result<JavacBuilder> {
1168         Strings opts = new Strings();
1169         List<JavaSourceFile> sourceFiles = new ArrayList<>();
1170         List<JavaFileObject> classes = new ArrayList<>();
1171         public ClassDir classDir;
1172 
1173         JavacResult(JavacBuilder builder) {
1174             super(builder);
1175         }
1176     }
1177 
1178     public static JavacResult javac(JavacBuilder javacBuilder) {
1179         JavacResult result = new JavacResult(javacBuilder);
1180 
1181         try {
1182             if (javacBuilder.source != null) {
1183                 result.opts.add("--source", javacBuilder.source.toString());
1184             }
1185 
1186             if (javacBuilder.enablePreview != null && javacBuilder.enablePreview) {
1187                 result.opts.add("--enable-preview");
1188             }
1189             if (javacBuilder.modules != null) {
1190                 javacBuilder.modules.strings.forEach(module ->
1191                         result.opts.add("--add-modules", module)
1192                 );
1193             }
1194 
1195             if (javacBuilder.exports != null) {
1196                 javacBuilder.exports.forEach(fpt -> {
1197                     result.opts.add("--add-exports=" + fpt.fromModule + "/" + fpt.pkg + "=" + fpt.toModule);
1198                 });
1199             }
1200 
1201             result.classDir = javacBuilder.classDir == null ? ClassDir.temp() : javacBuilder.classDir;
1202             result.opts.add("-d", result.classDir.path().toString());
1203             if (javacBuilder.classPath != null) {
1204                 result.opts.add("--class-path", javacBuilder.classPath.charSeparated());
1205             } else if (javacBuilder.modulePath != null) {
1206                 //https://dev.java/learn/modules/building/
1207                 result.opts.add("--module-path", javacBuilder.modulePath.charSeparated());
1208             } else {
1209                 // println("Warning no class path or module path ");
1210                 //throw new RuntimeException("No class path or module path provided");
1211             }
1212             var mavenStyleRoot =
1213                     ((javacBuilder.parent instanceof JarBuilder jarBuilder) && jarBuilder.mavenStyleRoot instanceof DirEntry fromJarBuilder)
1214                             ? fromJarBuilder
1215                             : javacBuilder.mavenStyleRoot;
1216 
1217 
1218             if (mavenStyleRoot == null) {
1219                 if (javacBuilder.sourcePath != null && !javacBuilder.sourcePath.entries.isEmpty()) {
1220                     result.opts.add("--source-path", javacBuilder.sourcePath.charSeparated());
1221                     result.sourceFiles.addAll(javacBuilder.sourcePath.javaFiles().map(JavaSourceFile::new).toList());
1222                 } else if (javacBuilder.moduleSourcePath != null && !javacBuilder.moduleSourcePath.entries.isEmpty()) {
1223                     result.opts.add("--module-source-path", javacBuilder.moduleSourcePath.charSeparated());
1224                     result.sourceFiles.addAll(javacBuilder.moduleSourcePath.javaFiles().map(JavaSourceFile::new).toList());
1225                 } else {
1226                     throw new RuntimeException("No source path or module source path specified");
1227                 }
1228             } else {
1229                 var sourcePath = SourcePath.of().add(SourceDir.of(mavenStyleRoot.path.resolve("src/main/java")));
1230                 result.sourceFiles.addAll(sourcePath.javaFiles().map(JavaSourceFile::new).toList());
1231                 if (result.sourceFiles.isEmpty()) {
1232                     throw new RuntimeException("No sources");
1233                 }
1234                 result.opts.add("--source-path", sourcePath.charSeparated());
1235 
1236                 if (javacBuilder.sourcePath != null && !javacBuilder.sourcePath.entries.isEmpty()) {
1237                     throw new RuntimeException("You have specified --source-path AND provided maven_style_root ");
1238                 }
1239             }
1240             boolean[] failed = {false};
1241 
1242             DiagnosticListener<JavaFileObject> diagnosticListener =
1243                     (diagnostic) -> {
1244                         if (diagnostic.getKind().equals(Diagnostic.Kind.ERROR)) {
1245                             failed[0] = true;
1246                         }
1247                         if (!diagnostic.getKind().equals(Diagnostic.Kind.NOTE)) {
1248                             System.out.println("javac "
1249                                     + diagnostic.getKind()
1250                                     + " "
1251                                     + ((JavaSourceFile) (diagnostic.getSource())).path().toString()
1252                                     + "  "
1253                                     + diagnostic.getLineNumber()
1254                                     + ":"
1255                                     + diagnostic.getColumnNumber()
1256                                     + " "
1257                                     + diagnostic.getMessage(null));
1258                         }
1259                     };
1260 
1261             JavaCompiler javac = ToolProvider.getSystemJavaCompiler();
1262             if (javacBuilder.exclusionFilters != null) {
1263                 javacBuilder.exclusionFilters.forEach(p -> {
1264                     result.sourceFiles = result.sourceFiles.stream().filter(
1265                             javaSourceFile -> {
1266                                 var kill = p.test(javaSourceFile);
1267                                 if (kill) {
1268                                     println("Excluded " + javaSourceFile);
1269                                 }
1270                                 return !kill;
1271                             }
1272                     ).toList();
1273                 });
1274             }
1275             if (javacBuilder.verbose || javacBuilder.parent instanceof JarBuilder jarBuilder && jarBuilder.verbose) {
1276                 print("javac " + result.opts.spaceSeparated());
1277                 result.sourceFiles.forEach(s -> print(s + " "));
1278                 println("");
1279             }
1280             JavaCompiler.CompilationTask compilationTask =
1281                     (javac.getTask(
1282                             new PrintWriter(System.err),
1283                             javac.getStandardFileManager(diagnosticListener, null, null),
1284                             diagnosticListener,
1285                             result.opts.strings,
1286                             null,
1287                             result.sourceFiles
1288                     ));
1289             JavacTask javacTask = (JavacTask) compilationTask;
1290 
1291             javacTask.generate().forEach(javaFileObject -> {
1292                 result.classes.add(javaFileObject);
1293             });
1294             if (failed[0]) {
1295                 throw new RuntimeException("javac failed");
1296             }
1297             return result;
1298         } catch (IOException e) {
1299             throw new RuntimeException(e);
1300         }
1301     }
1302 
1303     public static JavacBuilder javacBuilder(Consumer<JavacBuilder> javacBuilderConsumer) {
1304         JavacBuilder javacBuilder = new JavacBuilder();
1305         javacBuilderConsumer.accept(javacBuilder);
1306         return javacBuilder;
1307     }
1308 
1309     public static JavacResult javac(Consumer<JavacBuilder> javacBuilderConsumer) {
1310         return javac(javacBuilder(javacBuilderConsumer));
1311     }
1312 
1313     public static final class JavaBuilder extends JavaToolBuilder<JavaBuilder> {
1314         public String mainClass;
1315         public DirPath libraryPath;
1316         public boolean startOnFirstThread;
1317         public Strings args = new Strings();
1318         public Strings nativeAccessModules = new Strings();
1319         private boolean headless;
1320 
1321         public JavaBuilder enable_native_access(String module) {
1322             nativeAccessModules.add(module);
1323             return self();
1324         }
1325 
1326         public JavaBuilder enable_native_access_to_all_unnamed() {
1327             return enable_native_access("ALL-UNNAMED");
1328         }
1329 
1330         public JavaBuilder args(List<String> args) {
1331             this.args.add(args);
1332             return self();
1333         }
1334 
1335         public JavaBuilder args(String... args) {
1336             args(Arrays.asList(args));
1337             return self();
1338         }
1339 
1340         public JavaBuilder main_class(String mainClass) {
1341             this.mainClass = mainClass;
1342             return this;
1343         }
1344 
1345         public JavaBuilder library_path(List<DirPathHolder<?>> libraryPathEntries) {
1346             this.libraryPath = DirPath.ofOrUse(this.libraryPath).add(libraryPathEntries);
1347             return this;
1348         }
1349 
1350         public JavaBuilder library_path(DirPath libraryPathEntries) {
1351             this.libraryPath = DirPath.ofOrUse(this.libraryPath).add(libraryPathEntries);
1352             return this;
1353         }
1354 
1355         public JavaBuilder library_path(DirPathHolder<?>... libraryPathEntries) {
1356             return this.library_path(List.of(libraryPathEntries));
1357         }
1358 
1359         public JavaBuilder start_on_first_thread() {
1360             this.startOnFirstThread = true;
1361             return this;
1362         }
1363 
1364         public void headless() {
1365             this.headless = true;
1366         }
1367     }
1368 
1369     public static final class JavaResult extends Result<JavaBuilder> {
1370         Strings opts = new Strings();
1371 
1372         JavaResult(JavaBuilder javaBuilder) {
1373             super(javaBuilder);
1374         }
1375     }
1376 
1377     public static JavaBuilder java(JavaBuilder javaBuilder) {
1378         JavaResult result = new JavaResult(javaBuilder);
1379         result.opts.add(javaBuilder.jdk.path().resolve("bin/java").toString());
1380         if (javaBuilder.enablePreview != null && javaBuilder.enablePreview) {
1381             result.opts.add("--enable-preview");
1382         }
1383         if (javaBuilder.modules != null) {
1384             javaBuilder.modules.strings.forEach(module ->
1385                     result.opts.add("--add-modules", module)
1386             );
1387         }
1388 
1389         if (javaBuilder.exports != null) {
1390             javaBuilder.exports.forEach(fpt -> {
1391                 result.opts.add("--add-exports=" + fpt.fromModule + "/" + fpt.pkg + "=" + fpt.toModule);
1392             });
1393         }
1394         if (javaBuilder.headless) {
1395             result.opts.add("-Dheadless=true");
1396         }
1397         if (javaBuilder.startOnFirstThread) {
1398             result.opts.add("-XstartOnFirstThread");
1399         }
1400 
1401         javaBuilder.nativeAccessModules.strings.forEach(module ->
1402                 result.opts.add("--enable-native-access=" + module)
1403         );
1404 
1405         if (javaBuilder.classPath != null) {
1406             result.opts.add("--class-path", javaBuilder.classPath.charSeparated());
1407         }
1408         if (javaBuilder.libraryPath != null) {
1409             result.opts.add("-Djava.library.path=" + javaBuilder.libraryPath.charSeparated());
1410         }
1411         result.opts.add(javaBuilder.mainClass);
1412         result.opts.add(javaBuilder.args.strings);
1413 
1414         try {
1415             var processBuilder = new ProcessBuilder().inheritIO().command(result.opts.strings);
1416             var process = processBuilder.start();
1417             if (javaBuilder.verbose) {
1418                 println(result.opts.spaceSeparated());
1419             }
1420             process.waitFor();
1421         } catch (InterruptedException | IOException ie) {
1422             System.out.println(ie);
1423         }
1424 
1425         return javaBuilder;
1426     }
1427 
1428     public static JavaBuilder java(Consumer<JavaBuilder> javaBuilderConsumer) {
1429         JavaBuilder javaBuilder = new JavaBuilder();
1430         javaBuilderConsumer.accept(javaBuilder);
1431         return java(javaBuilder);
1432     }
1433 
1434     public static JavaBuilder javaBuilder() {
1435         return new JavaBuilder();
1436     }
1437 
1438     public static final class FormatBuilder extends Builder<FormatBuilder> {
1439         public SourcePath sourcePath;
1440 
1441         public FormatBuilder source_path(List<SourceDir> sourcePaths) {
1442             this.sourcePath = SourcePath.ofOrUse(this.sourcePath).add(sourcePaths);
1443             return this;
1444         }
1445 
1446         public FormatBuilder source_path(SourceDir... sourcePaths) {
1447             return source_path(List.of(sourcePaths));
1448         }
1449     }
1450 
1451     public static void format(RepoDir repoDir, Consumer<FormatBuilder> formatBuilderConsumer) {
1452         var formatBuilder = new FormatBuilder();
1453         formatBuilderConsumer.accept(formatBuilder);
1454         var classPathEntries = repoDir.classPathEntries("com.google.googlejavaformat/google-java-format");
1455 
1456         java($ -> $
1457                 .verbose()
1458                 .enable_preview()
1459                 .enable_native_access("ALL-UNNAMED")
1460                 .add_exports("java.base", "jdk.internal", "ALL-UNNAMED")
1461                 .add_exports(
1462                         "jdk.compiler",
1463                         List.of(
1464                                 "com.sun.tools.javac.api",
1465                                 "com.sun.tools.javac.code",
1466                                 "com.sun.tools.javac.file",
1467                                 "com.sun.tools.javac.main",
1468                                 "com.sun.tools.javac.parser",
1469                                 "com.sun.tools.javac.tree",
1470                                 "com.sun.tools.javac.util"),
1471                         "ALL-UNNAMED")
1472                 .class_path(classPathEntries)
1473                 .main_class("com.google.googlejavaformat.java.Main")
1474                 .args("-r")
1475                 .args(formatBuilder.sourcePath.javaFiles().map(Path::toString).toList()));
1476     }
1477 
1478     public static final class TestNGBuilder extends Builder<TestNGBuilder> {
1479         public SourcePath sourcePath;
1480         public ClassPath classPath;
1481         private SuiteBuilder suiteBuilder;
1482         private JarFile testJar;
1483 
1484         public TestNGBuilder class_path(List<ClassPathEntryProvider> classPathEntryProviders) {
1485             this.classPath = ClassPath.ofOrUse(this.classPath).add(classPathEntryProviders);
1486             return this;
1487         }
1488 
1489         public TestNGBuilder class_path(ClassPathEntryProvider... classPathEntryProviders) {
1490             class_path(List.of(classPathEntryProviders));
1491             return this;
1492         }
1493 
1494         public TestNGBuilder source_path(List<SourceDir> sourcePathEntries) {
1495             this.sourcePath = SourcePath.ofOrUse(this.sourcePath).add(sourcePathEntries);
1496             return this;
1497         }
1498 
1499         public TestNGBuilder source_path(SourceDir... sourcePathEntries) {
1500             return source_path(List.of(sourcePathEntries));
1501         }
1502 
1503         public TestNGBuilder testJar(JarFile testJar) {
1504             this.testJar = testJar;
1505             return self();
1506         }
1507 
1508         public static class SuiteBuilder {
1509             String name;
1510 
1511             SuiteBuilder name(String name) {
1512                 this.name = name;
1513                 return this;
1514             }
1515 
1516             List<TestBuilder> testBuilders = new ArrayList<>();
1517 
1518             public static class TestBuilder {
1519                 String name;
1520                 List<String> classNames;
1521 
1522                 TestBuilder name(String name) {
1523                     this.name = name;
1524                     return this;
1525                 }
1526 
1527                 public TestBuilder classes(List<String> classNames) {
1528                     this.classNames = this.classNames == null ? new ArrayList<>() : this.classNames;
1529                     this.classNames.addAll(classNames);
1530                     return this;
1531                 }
1532 
1533                 public TestBuilder classes(String... classNames) {
1534                     return classes(List.of(classNames));
1535                 }
1536             }
1537 
1538             public void test(String testName, Consumer<TestBuilder> testBuilderConsumer) {
1539                 TestBuilder testBuilder = new TestBuilder();
1540                 testBuilder.name(testName);
1541                 testBuilderConsumer.accept(testBuilder);
1542                 testBuilders.add(testBuilder);
1543             }
1544         }
1545 
1546         public TestNGBuilder suite(String suiteName, Consumer<SuiteBuilder> suiteBuilderConsumer) {
1547             this.suiteBuilder = new SuiteBuilder();
1548             suiteBuilder.name(suiteName);
1549             suiteBuilderConsumer.accept(suiteBuilder);
1550             return self();
1551         }
1552     }
1553 
1554     public static void testng(RepoDir repoDir, Consumer<TestNGBuilder> testNGBuilderConsumer) {
1555         var testNGBuilder = new TestNGBuilder();
1556         testNGBuilderConsumer.accept(testNGBuilder);
1557 
1558         var text =
1559                 XMLNode.create(
1560                                 "suite",
1561                                 $ -> {
1562                                     $.attr("name", testNGBuilder.suiteBuilder.name);
1563                                     testNGBuilder.suiteBuilder.testBuilders.forEach(
1564                                             tb -> {
1565                                                 $.element(
1566                                                         "test",
1567                                                         $$ ->
1568                                                                 $$.attr("name", tb.name)
1569                                                                         .element(
1570                                                                                 "classes",
1571                                                                                 $$$ ->
1572                                                                                         tb.classNames.forEach(
1573                                                                                                 className ->
1574                                                                                                         $$$.element(
1575                                                                                                                 "class",
1576                                                                                                                 $$$$ -> $$$$.attr("name", className)))));
1577                                             });
1578                                 })
1579                         .toString();
1580 
1581         println(text);
1582 
1583         TestNGSuiteFile testNGSuiteFile = TestNGSuiteFile.containing(text);
1584         var mavenJars = repoDir.classPathEntries("org.testng/testng", "org.slf4j/slf4j-api");
1585 
1586 
1587         var testJarResult =
1588                 jar(jar -> jar
1589                         .jarFile(testNGBuilder.testJar)
1590                         .javac(javac -> javac
1591                                 .source(24)
1592                                 .enable_preview()
1593                                 .class_path(testNGBuilder.classPath, mavenJars)
1594                                 .source_path(testNGBuilder.sourcePath)
1595                         )
1596                 );
1597 
1598         java(
1599                 $ ->
1600                         $.enable_preview()
1601                                 .add_exports_to_all_unnamed("java.base", "jdk.internal")
1602                                 .enable_native_access("ALL-UNNAMED")
1603                                 .class_path(testNGBuilder.classPath, mavenJars, testJarResult)
1604                                 .main_class("org.testng.TestNG")
1605                                 .args(testNGSuiteFile.path().toString()));
1606     }
1607 
1608     public static final class JarBuilder extends Builder<JarBuilder> {
1609         public static class Manifest {
1610             public String mainClass;
1611             public String[] classPath;
1612             public String version;
1613             public String createdBy;
1614             public String buildBy;
1615 
1616             public void writeTo(JarOutputStream jarStream) {
1617                 PrintWriter printWriter = new PrintWriter(jarStream);
1618                 if (version != null) {
1619                     printWriter.println("Manifest-Version: " + version);
1620                 }
1621                 if (mainClass != null) {
1622                     printWriter.println("Main-Class: " + mainClass);
1623                 }
1624                 if (classPath != null) {
1625                     printWriter.print("Class-Path:");
1626                     for (String s : classPath) {
1627                         printWriter.print(" ");
1628                         printWriter.print(s);
1629                     }
1630                     printWriter.println();
1631                 }
1632                 printWriter.flush();
1633             }
1634         }
1635 
1636         public static final class ManifestBuilder extends Builder<ManifestBuilder> {
1637 
1638             Manifest manifest;
1639 
1640             public ManifestBuilder main_class(String mainClass) {
1641                 this.manifest.mainClass = mainClass;
1642                 return self();
1643             }
1644 
1645             public ManifestBuilder version(String version) {
1646                 this.manifest.version = version;
1647                 return self();
1648             }
1649 
1650             public ManifestBuilder created_by(String createdBy) {
1651                 this.manifest.createdBy = createdBy;
1652                 return self();
1653             }
1654 
1655             public ManifestBuilder build_by(String buildBy) {
1656                 this.manifest.buildBy = buildBy;
1657                 return self();
1658             }
1659 
1660             public ManifestBuilder class_path(String... classPath) {
1661                 this.manifest.classPath = classPath;
1662                 return self();
1663             }
1664 
1665             public ManifestBuilder class_path(ClassPathEntry... classPathEntries) {
1666                 this.manifest.classPath = Stream.of(classPathEntries).map(classPathEntry -> classPathEntry.path().getFileName().toString()).toArray(String[]::new);
1667                 return self();
1668             }
1669 
1670             ManifestBuilder(Manifest manifest) {
1671                 this.manifest = manifest;
1672             }
1673         }
1674 
1675         public DirEntry mavenStyleRoot;
1676         public JarFile jar;
1677         public JavacResult javacResult;
1678         public DirPath dirList;
1679         //  public String mainClass;
1680         public Manifest manifest;
1681 
1682         public JarBuilder jarFile(JarFile jar) {
1683             this.jar = jar;
1684             return self();
1685         }
1686 
1687         public JarBuilder maven_style_root(DirEntry mavenStyleRoot) {
1688             this.mavenStyleRoot = mavenStyleRoot;
1689             return this;
1690         }
1691 
1692         public JarBuilder manifest(Consumer<ManifestBuilder> manifestBuilderConsumer) {
1693             this.manifest = this.manifest == null ? new Manifest() : this.manifest;
1694             var manifestBuilder = new ManifestBuilder(manifest);
1695             manifestBuilderConsumer.accept(manifestBuilder);
1696             return self();
1697         }
1698 
1699         private JarBuilder javac(JavacBuilder javacBuilder) {
1700             this.javacResult = Bldr.javac(javacBuilder);
1701 
1702             this.dirList =
1703                     (this.dirList == null)
1704                             ? DirPath.of().add(this.javacResult.classDir)
1705                             : this.dirList.add(this.javacResult.classDir);
1706             if (mavenStyleRoot != null) {
1707                 var resources = mavenStyleRoot.dir("src/main/resources");
1708                 if (resources.exists()) {
1709                     this.dirList.add(resources);
1710                 }
1711             }
1712             return self();
1713         }
1714 
1715         public JavacBuilder javacBuilder(Consumer<JavacBuilder> javacBuilderConsumer) {
1716             JavacBuilder javacBuilder = new JavacBuilder(this);
1717             javacBuilderConsumer.accept(javacBuilder);
1718             return javacBuilder;
1719         }
1720 
1721         public JavacBuilder javacBuilder(JavacBuilder copyMe, Consumer<JavacBuilder> javacBuilderConsumer) {
1722             JavacBuilder javacBuilder = new JavacBuilder(this);
1723             javacBuilder.dontCallThisCopy(copyMe);
1724             javacBuilderConsumer.accept(javacBuilder);
1725             return javacBuilder;
1726         }
1727 
1728         public JarBuilder javac(Consumer<JavacBuilder> javacBuilderConsumer) {
1729             return javac(javacBuilder(javacBuilderConsumer));
1730         }
1731 
1732         public JarBuilder javac(JavacBuilder copyMe, Consumer<JavacBuilder> javacBuilderConsumer) {
1733             return javac(javacBuilder(copyMe, javacBuilderConsumer));
1734         }
1735 
1736         @SuppressWarnings("unchecked")
1737         public <P extends DirPathHolder<P>> JarBuilder dir_list(P... holders) {
1738             Arrays.asList(holders).forEach(holder ->
1739                     this.dirList = DirPath.ofOrUse(this.dirList).add(holder)
1740             );
1741             return self();
1742         }
1743 
1744         @SuppressWarnings("unchecked")
1745         public <P extends DirPathHolder<P>> JarBuilder add(P... holders) {
1746             Arrays.asList(holders).forEach(holder ->
1747                     this.dirList = DirPath.ofOrUse(this.dirList).add(holder)
1748             );
1749             return self();
1750         }
1751     }
1752 
1753     public static final class JarResult extends Result<JarBuilder> implements ClassPathEntryProvider {
1754         public Strings opts = new Strings();
1755         public List<RootDirAndSubPath> pathsToJar = new ArrayList<>();
1756         public List<Path> paths = new ArrayList<>();
1757         public JarFile jarFile;
1758 
1759 
1760         public JarResult(JarBuilder jarBuilder) {
1761             super(jarBuilder);
1762             this.jarFile = jarBuilder.jar;
1763         }
1764 
1765         @Override
1766         public List<ClassPathEntry> classPathEntries() {
1767             return List.of(jarFile);
1768         }
1769 
1770         @Override
1771         public String toString() {
1772             return jarFile.path.toString();
1773         }
1774     }
1775 
1776     public static JarResult jar(JarBuilder jarBuilder) {
1777 
1778         JarResult result = new JarResult(jarBuilder);
1779         try {
1780 
1781             var jarStream = new JarOutputStream(Files.newOutputStream(jarBuilder.jar.path()));
1782             if (jarBuilder.dirList == null) {
1783                 throw new RuntimeException("Nothing to jar ");
1784             }
1785             if (jarBuilder.manifest != null) {
1786                 // We must add manifest
1787                 var entry = new JarEntry("META-INF/MANIFEST.MF");
1788                 // entry.setTime(Files.getLastModifiedTime(rootAndPath.path()).toMillis());
1789 
1790                 jarStream.putNextEntry(entry);
1791                 jarBuilder.manifest.writeTo(jarStream);
1792                 jarStream.closeEntry();
1793 
1794             }
1795             jarBuilder.dirList.entries.forEach(
1796                     root ->
1797                             root.findFiles()
1798                                     .map(path -> new RootDirAndSubPath(root, path))
1799                                     .forEach(result.pathsToJar::add));
1800             result.pathsToJar.stream()
1801                     .sorted(Comparator.comparing(RootDirAndSubPath::path))
1802                     .forEach(
1803                             rootAndPath -> {
1804                                 try {
1805                                     result.paths.add(rootAndPath.path);
1806                                     var entry = new JarEntry(rootAndPath.relativize().toString());
1807                                     entry.setTime(Files.getLastModifiedTime(rootAndPath.path()).toMillis());
1808                                     jarStream.putNextEntry(entry);
1809                                     Files.newInputStream(rootAndPath.path()).transferTo(jarStream);
1810                                     jarStream.closeEntry();
1811                                     if (jarBuilder.verbose) {
1812                                         println("INFO: adding " + rootAndPath.relativize().toString());
1813                                     }
1814                                 } catch (IOException e) {
1815                                     throw new RuntimeException(e);
1816                                 }
1817                             });
1818             jarStream.finish();
1819             jarStream.close();
1820             if (jarBuilder.verbose) {
1821                 println("INFO: created " + jarBuilder.jar.path.toString());
1822             }
1823             return result;
1824         } catch (IOException e) {
1825             throw new RuntimeException(e);
1826         }
1827     }
1828 
1829     public static JarBuilder jarBuilder(Consumer<JarBuilder> jarBuilderConsumer) {
1830         JarBuilder jarBuilder = new JarBuilder();
1831         jarBuilderConsumer.accept(jarBuilder);
1832         return jarBuilder;
1833     }
1834 
1835     public static JarBuilder jarBuilder(JarBuilder copyMe, Consumer<JarBuilder> jarBuilderConsumer) {
1836         JarBuilder jarBuilder = new JarBuilder();
1837         jarBuilder.dontCallThisCopy(copyMe);
1838         jarBuilderConsumer.accept(jarBuilder);
1839         return jarBuilder;
1840     }
1841 
1842     public static JarResult jar(Consumer<JarBuilder> jarBuilderConsumer) {
1843         return jar(jarBuilder(jarBuilderConsumer));
1844     }
1845 
1846     public static JarResult jar(JarBuilder copyMe, Consumer<JarBuilder> jarBuilderConsumer) {
1847         return jar(jarBuilder(copyMe, jarBuilderConsumer));
1848     }
1849 
1850     public static final class CMakeBuilder extends Builder<CMakeBuilder> {
1851         public List<String> libraries = new ArrayList<>();
1852         public CMakeBuildDir cmakeBuildDir;
1853         public DirEntry sourceDir;
1854         private Path output;
1855         public BuildDir copyToDir;
1856         public List<String> opts = new ArrayList<>();
1857 
1858         public CMakeBuilder opts(List<String> opts) {
1859             this.opts.addAll(opts);
1860             return self();
1861         }
1862 
1863         public CMakeBuilder opts(String... opts) {
1864             opts(Arrays.asList(opts));
1865             return self();
1866         }
1867 
1868         public CMakeBuilder() {
1869             opts.add("cmake");
1870         }
1871 
1872         public CMakeBuilder build_dir(CMakeBuildDir cmakeBuildDir) {
1873             this.cmakeBuildDir = cmakeBuildDir;
1874             opts("-B", cmakeBuildDir.path.toString());
1875             return this;
1876         }
1877 
1878         public CMakeBuilder copy_to(BuildDir copyToDir) {
1879             this.copyToDir = copyToDir;
1880             opts("-DHAT_TARGET=" + this.copyToDir.path().toString());
1881             return this;
1882         }
1883 
1884         public CMakeBuilder source_dir(DirEntry sourceDir) {
1885             this.sourceDir = sourceDir;
1886             opts("-S", sourceDir.path().toString());
1887             return this;
1888         }
1889 
1890         public CMakeBuilder build(CMakeBuildDir cmakeBuildDir) {
1891             this.cmakeBuildDir = cmakeBuildDir;
1892             opts("--build", cmakeBuildDir.path().toString());
1893             return this;
1894         }
1895     }
1896 
1897     public static void cmake(Consumer<CMakeBuilder> cmakeBuilderConsumer) {
1898         CMakeBuilder cmakeBuilder = new CMakeBuilder();
1899         cmakeBuilderConsumer.accept(cmakeBuilder);
1900         cmakeBuilder.cmakeBuildDir.create();
1901         try {
1902             var processBuilder = new ProcessBuilder().inheritIO().command(cmakeBuilder.opts);
1903             var process = processBuilder.start();
1904             if (cmakeBuilder.verbose) {
1905                 print(cmakeBuilder.opts);
1906             }
1907             process.waitFor();
1908         } catch (InterruptedException | IOException ie) {
1909             System.out.println(ie);
1910         }
1911     }
1912 
1913     static Path unzip(Path in, Path dir) {
1914         try {
1915             Files.createDirectories(dir);
1916             ZipFile zip = new ZipFile(in.toFile());
1917             zip.entries()
1918                     .asIterator()
1919                     .forEachRemaining(
1920                             entry -> {
1921                                 try {
1922                                     String currentEntry = entry.getName();
1923 
1924                                     Path destFile = dir.resolve(currentEntry);
1925                                     // destFile = new File(newPath, destFile.getName());
1926                                     Path destinationParent = destFile.getParent();
1927                                     Files.createDirectories(destinationParent);
1928                                     // create the parent directory structure if needed
1929 
1930                                     if (!entry.isDirectory()) {
1931                                         zip.getInputStream(entry).transferTo(Files.newOutputStream(destFile));
1932                                     }
1933                                 } catch (IOException ioe) {
1934                                     throw new RuntimeException(ioe);
1935                                 }
1936                             });
1937             zip.close();
1938 
1939         } catch (IOException e) {
1940             throw new RuntimeException(e);
1941         }
1942         return dir;
1943     }
1944 
1945 
1946     public static final class JExtractBuilder extends Builder<JExtractBuilder> {
1947         public Strings compileFlags = new Strings();
1948         public List<Path> libraries = new ArrayList<>();
1949         public List<Path> headers = new ArrayList<>();
1950         private String targetPackage;
1951         private String headerClassName;
1952         private BuildDir output;
1953 
1954         protected JExtractBuilder dontCallThisCopy(JExtractBuilder other) {
1955             this.compileFlags = new Strings(other.compileFlags);
1956             if (other.targetPackage != null) {
1957                 throw new RuntimeException("You are copying jextract builder already bound to a target package");
1958             }
1959             if (other.output != null) {
1960                 throw new RuntimeException("You are copying jextract builder already bound to output directory");
1961             }
1962             if (!other.libraries.isEmpty()) {
1963                 throw new RuntimeException("You are copying jextract builder already bound to library(ies)");
1964             }
1965             if (!other.headers.isEmpty()) {
1966                 throw new RuntimeException("You are copying jextract builder already bound to headers library(ies)");
1967             }
1968             return self();
1969         }
1970 
1971         public JExtractBuilder target_package(String targetPackage) {
1972             this.targetPackage = targetPackage;
1973             return self();
1974         }
1975 
1976         public JExtractBuilder header_class_name(String headerClassName) {
1977             this.headerClassName = headerClassName;
1978             return self();
1979         }
1980 
1981         public JExtractBuilder output(BuildDir output) {
1982             this.output = output;
1983             return self();
1984         }
1985 
1986         public JExtractBuilder library(Path... libraries) {
1987             this.libraries.addAll(Arrays.asList(libraries));
1988             return self();
1989         }
1990 
1991         public JExtractBuilder compile_flag(String... compileFlags) {
1992             this.compileFlags.add(compileFlags);
1993             return self();
1994         }
1995 
1996         public JExtractBuilder header(Path header) {
1997             this.headers.add(header);
1998             return self();
1999         }
2000 
2001         public JExtractBuilder capability(Capabilities.Jextractable jextractable, BuildDir stageDir) {
2002              output(jextractable.stage(stageDir))
2003                     .target_package(jextractable.packageName())
2004                     .header_class_name(jextractable.headerClassName());
2005               jextractable.inversionOfControl(this);
2006              return self();
2007         }
2008     }
2009 
2010     public static final class JExtractResult extends Result<JExtractBuilder> {
2011         public Strings opts = new Strings();
2012 
2013         JExtractResult(JExtractBuilder builder) {
2014             super(builder);
2015         }
2016     }
2017 
2018     public static JExtractResult jextract(Capabilities.JExtract jextract, Consumer<JExtractBuilder> jextractBuilderConsumer) {
2019       return jextract(JExtractExecutable.of(jextract.path()),jextractBuilderConsumer);
2020     }
2021 
2022 
2023     public static JExtractResult jextract(JExtractExecutable executable, Consumer<JExtractBuilder> jextractBuilderConsumer) {
2024 
2025         var exePath = executable.path;
2026         var homePath = exePath.getParent().getParent();
2027 
2028         JExtractBuilder jExtractBuilder = new JExtractBuilder();
2029         JExtractResult result = new JExtractResult(jExtractBuilder);
2030         jextractBuilderConsumer.accept(jExtractBuilder);
2031         result.opts.add(executable.path().toString());
2032 
2033         if (jExtractBuilder.targetPackage != null) {
2034             result.opts.add("--target-package", jExtractBuilder.targetPackage);
2035         }
2036         if (jExtractBuilder.output != null) {
2037             jExtractBuilder.output.create();
2038             result.opts.add("--output", jExtractBuilder.output.path().toString());
2039         }
2040         for (Path library : jExtractBuilder.libraries) {
2041             result.opts.add("--library", ":" + library);
2042         }
2043 
2044 
2045         if (jExtractBuilder.headers.isEmpty()) {
2046             throw new RuntimeException("No headers specified");
2047         }
2048         for (Path header : jExtractBuilder.headers) {
2049             if (jExtractBuilder.headerClassName != null) {
2050                 result.opts.add("--header-class-name", jExtractBuilder.headerClassName);
2051                 System.out.println("header and class name: " + header.toString() + " with " + jExtractBuilder.headerClassName);
2052             } else {
2053                 System.out.println("header: " + header.toString() + " no header className");
2054             }
2055             result.opts.add(header.toString());
2056         }
2057 
2058 
2059         if (jExtractBuilder.compileFlags != null && !jExtractBuilder.compileFlags.strings.isEmpty()) {
2060             jExtractBuilder.output.textFile("compile_flags.txt", jExtractBuilder.compileFlags.strings);
2061         }
2062 
2063         if (jExtractBuilder.verbose) {
2064             println(result.opts.spaceSeparated());
2065         }
2066         var processBuilder = new ProcessBuilder();
2067         if (jExtractBuilder.output != null) {
2068             processBuilder.directory(jExtractBuilder.output.path().toFile());
2069         }
2070         processBuilder.inheritIO().command(result.opts.strings);
2071         try {
2072             processBuilder.start().waitFor();
2073         } catch (InterruptedException | IOException ie) {
2074             throw new RuntimeException(ie);
2075         }
2076         return result;
2077     }
2078 
2079     public record SearchableTextFile(Path path) implements TextFile {
2080         static SearchableTextFile of(Path path) {
2081             return new SearchableTextFile(path);
2082         }
2083 
2084         public Stream<Line> lines() {
2085             try {
2086                 int num[] = new int[]{1};
2087                 return Files.readAllLines(path(), StandardCharsets.UTF_8).stream()
2088                         .map(line -> new Line(line, num[0]++));
2089             } catch (IOException ioe) {
2090                 System.out.println(ioe);
2091                 return new ArrayList<Line>().stream();
2092             }
2093         }
2094 
2095         public boolean grep(Pattern pattern) {
2096             return lines().anyMatch(line -> pattern.matcher(line.line).matches());
2097         }
2098 
2099         public boolean hasSuffix(String... suffixes) {
2100             var suffixSet = Set.of(suffixes);
2101             int dotIndex = path().toString().lastIndexOf('.');
2102             return dotIndex == -1 || suffixSet.contains(path().toString().substring(dotIndex + 1));
2103         }
2104     }
2105 
2106     public record Line(String line, int num) {
2107         public boolean grep(Pattern pattern) {
2108             return pattern.matcher(line()).matches();
2109         }
2110     }
2111 
2112     public static Path curl(URL url, Path file) {
2113         try {
2114             println("Downloading " + url + "->" + file);
2115             url.openStream().transferTo(Files.newOutputStream(file));
2116         } catch (IOException e) {
2117             throw new RuntimeException(e);
2118         }
2119         return file;
2120     }
2121 
2122     public static Optional<Path> which(String execName) {
2123         // which and whereis had issues.
2124         return Arrays.asList(System.getenv("PATH").split(File.pathSeparator)).stream()
2125                 .map(dirName -> Path.of(dirName).resolve(execName).normalize())
2126                 .filter(Files::isExecutable)
2127                 .findFirst();
2128     }
2129 
2130     public static boolean canExecute(String execName) {
2131         return which(execName).isPresent();
2132     }
2133 
2134     public static Path untar(Path tarFile, Path dir) {
2135         try {
2136             new ProcessBuilder()
2137                     .inheritIO()
2138                     .command("tar", "xvf", tarFile.toString(), "--directory", tarFile.getParent().toString())
2139                     .start()
2140                     .waitFor();
2141             return dir;
2142         } catch (
2143                 InterruptedException
2144                         e) { // We get IOException if the executable not found, at least on Mac so interuppted
2145             // means it exists
2146             return null;
2147         } catch (IOException e) { // We get IOException if the executable not found, at least on Mac
2148             // throw new RuntimeException(e);
2149             return null;
2150         }
2151     }
2152 
2153     public static Optional<Path> fromPATH(String name) {
2154         return Arrays.stream(System.getenv("PATH").split(File.pathSeparator))
2155                 .map(dirName -> Path.of(dirName).resolve(name).normalize())
2156                 .filter(Files::isExecutable).findFirst();
2157     }
2158 
2159 
2160     public static <T extends PathHolder> T assertExists(T testme) {
2161         if (Files.exists(testme.path())) {
2162             return testme;
2163         } else {
2164             throw new IllegalStateException("FAILED: " + testme.path() + " does not exist");
2165         }
2166     }
2167 
2168     public static <T extends Path> T assertExists(T path) {
2169         if (Files.exists(path)) {
2170             return path;
2171         } else {
2172             throw new IllegalStateException("FAILED: " + path + " does not exist");
2173         }
2174     }
2175 
2176     public static class CMakeProbe implements Capabilities.Probe {
2177         public interface CMakeVar<T> {
2178             String name();
2179 
2180             T value();
2181         }
2182 
2183         public record CMakeTypedVar(String name, String type, String value, String comment)
2184                 implements CMakeVar<String> {
2185             static final Regex regex = Regex.of("^_*(?:CMAKE_)?([A-Za-z0-9_]+):([^=]*)=(.*)$");
2186 
2187             CMakeTypedVar(Matcher matcher, String comment) {
2188                 this(
2189                         "CMAKE_" + matcher.group(1).trim(),
2190                         matcher.group(2).trim(),
2191                         matcher.group(3).trim(),
2192                         comment.substring(2).trim());
2193             }
2194 
2195             static boolean onMatch(String line, String comment, Consumer<CMakeTypedVar> consumer) {
2196                 return regex.matches(line, matcher -> consumer.accept(new CMakeTypedVar(matcher, comment)));
2197             }
2198         }
2199 
2200         public record CMakeSimpleVar(String name, String value) implements CMakeVar {
2201             static final Regex regex = Regex.of("^_*(?:CMAKE_)?([A-Za-z0-9_]+)=\\{<\\{(.*)\\}>\\}$");
2202 
2203             CMakeSimpleVar(Matcher matcher) {
2204                 this(
2205                         "CMAKE_" + matcher.group(1).trim(),
2206                         (matcher.group(2).isEmpty()) ? "" : matcher.group(2).trim());
2207             }
2208 
2209             static boolean onMatch(String line, String comment, Consumer<CMakeSimpleVar> consumer) {
2210                 return regex.matches(line, matcher -> consumer.accept(new CMakeSimpleVar(matcher)));
2211             }
2212         }
2213 
2214         public record CMakeDirVar(String name, DirPathHolder value) implements CMakeVar {
2215             static final Regex regex = Regex.of("^_*(?:CMAKE_)?([A-Za-z0-9_]+)=\\{<\\{(.*)\\}>\\}$");
2216 
2217             static boolean onMatch(String line, String comment, Consumer<CMakeSimpleVar> consumer) {
2218                 return regex.matches(line, matcher -> consumer.accept(new CMakeSimpleVar(matcher)));
2219             }
2220         }
2221 
2222         public record CMakeContentVar(String name, String value) implements CMakeVar {
2223             static final Regex startRegex = Regex.of("^_*(?:CMAKE_)?([A-Za-z0-9_]+)=\\{<\\{(.*)$");
2224             static final Regex endRegex = Regex.of("^(.*)\\}>\\}$");
2225         }
2226 
2227         public record CMakeRecipeVar(String name, String value) implements CMakeVar<String> {
2228             static final Regex varPattern = Regex.of("<([^>]*)>");
2229             static final Regex regex = Regex.of("^_*(?:CMAKE_)?([A-Za-z0-9_]+)=\\{<\\{<(.*)>\\}>\\}$");
2230 
2231             CMakeRecipeVar(Matcher matcher) {
2232                 this(
2233                         "CMAKE_" + matcher.group(1).trim(),
2234                         "<" + ((matcher.group(2).isEmpty()) ? "" : matcher.group(2).trim()) + ">");
2235             }
2236 
2237             public String expandRecursively(Map<String, CMakeVar<?>> varMap, String value) { // recurse
2238                 String result = value;
2239                 if (varPattern.pattern().matcher(value) instanceof Matcher matcher && matcher.find()) {
2240                     var v = matcher.group(1);
2241                     if (varMap.containsKey(v)) {
2242                         String replacement = varMap.get(v).value().toString();
2243                         result =
2244                                 expandRecursively(
2245                                         varMap,
2246                                         value.substring(0, matcher.start())
2247                                                 + replacement
2248                                                 + value.substring(matcher.end()));
2249                     }
2250                 }
2251                 return result;
2252             }
2253 
2254             public String expand(Map<String, CMakeVar<?>> vars) {
2255                 return expandRecursively(vars, value());
2256             }
2257 
2258             static boolean onMatch(String line, String comment, Consumer<CMakeRecipeVar> consumer) {
2259                 return regex.matches(line, matcher -> consumer.accept(new CMakeRecipeVar(matcher)));
2260             }
2261         }
2262 
2263         BuildDir dir;
2264 
2265         Map<String, CMakeVar<?>> varMap = new HashMap<>();
2266 
2267         public CMakeProbe(BuildDir dir, Capabilities capabilities) {
2268             this.dir = BuildDir.of(dir.path("cmakeprobe"));
2269             this.dir.clean();
2270 
2271             try {
2272                 this.dir.cmakeLists(cmakeLists -> {
2273                     cmakeLists.append(
2274                             """
2275                                     cmake_minimum_required(VERSION 3.21)
2276                                     project(cmakeprobe)
2277                                     set(CMAKE_CXX_STANDARD 14)
2278                                     """
2279                     );
2280 
2281                     capabilities.capabilities()
2282                             .filter(capability -> capability instanceof Capabilities.CMakeProbeable)
2283                             .map(capability -> (Capabilities.CMakeProbeable) capability)
2284                             .forEach(p ->
2285                                     cmakeLists.append(p.cmakeStanza()).append("\n")
2286                             );
2287                     cmakeLists.append(
2288                             """
2289                                     get_cmake_property(_variableNames VARIABLES ${VarNames})
2290                                     foreach(VarName ${_variableNames})
2291                                         message("${VarName}={<{${${VarName}}}>}")
2292                                     endforeach()
2293                                     """
2294                     );
2295                 });
2296 
2297                 var cmakeProcessBuilder =
2298                         new ProcessBuilder()
2299                                 .directory(this.dir.path().toFile())
2300                                 .redirectErrorStream(true)
2301                                 .command("cmake", "-LAH")
2302                                 .start();
2303                 List<String> stdinlines =
2304                         new BufferedReader(new InputStreamReader(cmakeProcessBuilder.getInputStream()))
2305                                 .lines()
2306                                 .toList();
2307                 cmakeProcessBuilder.waitFor();
2308                 this.dir.textFile("rawlines", sb -> {
2309                     stdinlines.forEach(line -> sb.append(line).append("\n"));
2310                     // stderrlines.forEach(line-> sb.append("ERR").append(line).append("\n"));
2311                 });
2312 
2313                 String comment = null;
2314                 String contentName = null;
2315                 StringBuilder content = null;
2316 
2317                 for (String line : stdinlines) {
2318                     if (line.startsWith("//")) {
2319                         comment = line;
2320                         content = null;
2321 
2322                     } else if (comment != null) {
2323                         if (CMakeTypedVar.onMatch(
2324                                 line,
2325                                 comment,
2326                                 v -> {
2327                                     if (varMap.containsKey(v.name())) {
2328                                         var theVar = varMap.get(v.name());
2329                                         if (theVar.value().equals(v.value())) {
2330                                           /*  println(
2331                                                     "replacing duplicate variable with typed variant with the name same value"
2332                                                             + v
2333                                                             + theVar);*/
2334                                         } else {
2335                                             throw new IllegalStateException(
2336                                                     "Duplicate variable name different value: " + v + theVar);
2337                                         }
2338                                         varMap.put(v.name(), v);
2339                                     } else {
2340                                         varMap.put(v.name(), v);
2341                                     }
2342                                 })) {
2343                         } else {
2344                             println("failed to parse " + line);
2345                         }
2346                         comment = null;
2347                         content = null;
2348                         contentName = null;
2349                     } else if (!line.isEmpty()) {
2350                         if (content != null) {
2351                             if (CMakeContentVar.endRegex.pattern().matcher(line) instanceof Matcher matcher
2352                                     && matcher.matches()) {
2353                                 content.append("\n").append(matcher.group(1));
2354                                 var v = new CMakeContentVar(contentName, content.toString());
2355                                 contentName = null;
2356                                 content = null;
2357                                 varMap.put(v.name(), v);
2358                             } else {
2359                                 content.append("\n").append(line);
2360                             }
2361                         } else if (!line.endsWith("}>}")
2362                                 && CMakeContentVar.startRegex.pattern().matcher(line) instanceof Matcher matcher
2363                                 && matcher.matches()) {
2364                             contentName = "CMAKE_" + matcher.group(1);
2365                             content = new StringBuilder(matcher.group(2));
2366                         } else if (CMakeRecipeVar.regex.pattern().matcher(line) instanceof Matcher matcher
2367                                 && matcher.matches()) {
2368                             CMakeVar<String> v = new CMakeRecipeVar(matcher);
2369                             if (varMap.containsKey(v.name())) {
2370                                 var theVar = varMap.get(v.name());
2371                                 if (theVar.value().equals(v.value())) {
2372                                     //  println("Skipping duplicate variable name different value: " + v + theVar);
2373                                 } else {
2374                                     throw new IllegalStateException(
2375                                             "Duplicate variable name different value: " + v + theVar);
2376                                 }
2377                                 varMap.put(v.name(), v);
2378                             } else {
2379                                 varMap.put(v.name(), v);
2380                             }
2381                         } else if (CMakeSimpleVar.regex.pattern().matcher(line) instanceof Matcher matcher
2382                                 && matcher.matches()) {
2383                             var v = new CMakeSimpleVar(matcher);
2384                             if (varMap.containsKey(v.name())) {
2385                                 var theVar = varMap.get(v.name());
2386                                 if (theVar.value().equals(v.value())) {
2387                                     // println("Skipping duplicate variable name different value: " + v + theVar);
2388                                 } else {
2389                                     //throw new IllegalStateException(
2390                                     //      "Duplicate variable name different vars: " + v + theVar);
2391                                 }
2392                                 // note we don't replace a Typed with a Simple
2393                             } else {
2394                                 varMap.put(v.name(), v);
2395                             }
2396                         } else {
2397                             // println("Skipping " + line);
2398                         }
2399                     }
2400                 }
2401 
2402             } catch (IOException ioe) {
2403                 throw new RuntimeException(ioe);
2404             } catch (InterruptedException e) {
2405                 throw new RuntimeException(e);
2406             }
2407             this.dir.textFile("vars", sb -> {
2408                 varMap.values().forEach(v -> sb.append(v.name()).append("<{<").append(v.value().toString()).append(">}>").append("\n"));
2409             });
2410 
2411             capabilities
2412                     .capabilities()
2413                     .filter(capability -> capability instanceof Capabilities.CMakeProbeable)
2414                     .map(capability -> (Capabilities.CMakeProbeable) capability)
2415                     .forEach(capability -> capability.accept(this));
2416 
2417         }
2418 
2419         ObjectFile cxxCompileObject(
2420                 ObjectFile target, CppSourceFile source, List<String> frameworks) {
2421             CMakeRecipeVar compileObject = (CMakeRecipeVar) varMap.get("CMAKE_CXX_COMPILE_OBJECT");
2422             Map<String, CMakeVar<?>> localVars = new HashMap<>(varMap);
2423             localVars.put("DEFINES", new CMakeSimpleVar("DEFINES", ""));
2424             localVars.put("INCLUDES", new CMakeSimpleVar("INCLUDES", ""));
2425             localVars.put("FLAGS", new CMakeSimpleVar("FLAGS", ""));
2426             localVars.put("OBJECT", new CMakeSimpleVar("OBJECT", target.path().toString()));
2427             localVars.put("SOURCE", new CMakeSimpleVar("SOURCE", source.path().toString()));
2428             String executable = compileObject.expand(localVars);
2429             println(executable);
2430             return target;
2431         }
2432 
2433         ExecutableFile cxxLinkExecutable(
2434                 ExecutableFile target, List<ObjectFile> objFiles, List<String> frameworks) {
2435             CMakeRecipeVar linkExecutable = (CMakeRecipeVar) varMap.get("CMAKE_CXX_LINK_EXECUTABLE");
2436             Map<String, CMakeVar<?>> localVars = new HashMap<>(varMap);
2437             String executable = linkExecutable.expand(localVars);
2438             println(executable);
2439             return target;
2440         }
2441 
2442         SharedLibraryFile cxxCreateSharedLibrary(
2443                 SharedLibraryFile target, List<ObjectFile> objFiles, List<String> frameworks) {
2444             CMakeRecipeVar createSharedLibrary =
2445                     (CMakeRecipeVar) varMap.get("CMAKE_CXX_CREATE_SHARED_LIBRARY");
2446             Map<String, CMakeVar<?>> localVars = new HashMap<>(varMap);
2447             String executable = createSharedLibrary.expand(localVars);
2448             println(executable);
2449             return target;
2450         }
2451 
2452 
2453         public String value(String key) {
2454             var v = varMap.get(key);
2455             return v.value().toString();
2456         }
2457 
2458         public boolean hasKey(String includeDirKey) {
2459             return varMap.containsKey(includeDirKey);
2460         }
2461 
2462     }
2463 
2464     public interface CapabilityHolder {
2465         Capabilities.Capability capability();
2466     }
2467 
2468     public static class Capabilities {
2469 
2470         interface Probe {
2471 
2472         }
2473 
2474         public static abstract class Capability implements CapabilityHolder {
2475             final public String name;
2476 
2477             protected Capability(String name) {
2478                 this.name = name;
2479             }
2480 
2481             public String name() {
2482                 return name;
2483             }
2484 
2485             public abstract boolean available();
2486 
2487             @Override
2488             public Capability capability() {
2489                 return this;
2490             }
2491         }
2492 
2493         public interface CMakeProbeable extends Consumer<Bldr.CMakeProbe> {
2494 
2495             // void setCmakeProbe(Bldr.CMakeProbe cmakeProbe);
2496             String cmakeStanza();
2497         }
2498 
2499         public interface Jextractable {
2500 
2501 
2502             String name();
2503 
2504 
2505             default BuildDir stage(BuildDir stage) {
2506                 return stage.buildDir(packageName() + "_jextracted");
2507             }
2508 
2509 
2510             default String packageName() {
2511                 return name().toLowerCase();
2512             }
2513 
2514 
2515             default String headerClassName() {
2516                 return packageName() + "_h";
2517             }
2518 
2519             default JarFile jarFile(BuildDir buildDir) {
2520                 return buildDir.jarFile("hat-jextracted-"+packageName() + "-1.0.jar");
2521             }
2522 
2523             void inversionOfControl(JExtractBuilder jextractBuilder);
2524         }
2525 
2526         public Map<String, Capability> capabilityMap = new HashMap<>();
2527 
2528         public static Capabilities of(CapabilityHolder... capabilityHolders) {
2529             return new Capabilities(capabilityHolders);
2530         }
2531 
2532         public Stream<Capability> capabilities() {
2533             return capabilityMap.values().stream();
2534         }
2535 
2536         public Stream<Capability> capabilities(Predicate<Capability> filter) {
2537             return capabilities().filter(filter);
2538         }
2539 
2540         public boolean capabilityIsAvailable(String name) {
2541             return capabilities().anyMatch(c -> c.name.equalsIgnoreCase(name));
2542         }
2543 
2544         private Capabilities(CapabilityHolder... capabilityHolders) {
2545             List.of(capabilityHolders).forEach(capabilityHolder ->
2546                     capabilityMap.put(capabilityHolder.capability().name, capabilityHolder.capability())
2547             );
2548 
2549         }
2550 
2551         public static final class OpenCL extends Capability implements CMakeProbeable, Jextractable {
2552             public static String includeDirKey = "CMAKE_OpenCL_INCLUDE_DIR";
2553             public static String libKey = "CMAKE_OpenCL_LIBRARY";
2554             public static String foundKey = "CMAKE_OPENCL_FOUND";
2555             public static String osxSysroot = "CMAKE_OSX_SYSROOT";
2556 
2557             public OpenCL() {
2558                 super("OpenCL");
2559             }
2560 
2561             public static OpenCL of() {
2562                 return new OpenCL();
2563             }
2564 
2565             @Override
2566             public String cmakeStanza() {
2567                 return
2568                         """
2569                                 find_package(OpenCL)
2570                                 if(OPENCL_FOUND)
2571                                     if (APPLE)
2572                                        set(OPENCL_INCLUDE_DIR "-framework OpenCL")
2573                                        set(OPENCL_LIBRARY_DIR "-framework OpenCL")
2574                                     else()
2575                                        set(OPENCL_LIB "OpenCL")
2576                                     endif()
2577                                 endif()
2578                                 """;
2579             }
2580 
2581             public String appLibFrameworks() {
2582                 return cmakeProbe.value(osxSysroot);
2583             }
2584 
2585             @Override
2586             public boolean available() {
2587                 return cmakeProbe.hasKey(foundKey) && cmakeProbe.value(foundKey).equals("TRUE");
2588             }
2589 
2590             public String lib() {
2591                 return cmakeProbe.value(libKey);
2592             }
2593 
2594             public String includeDir() {
2595                 return cmakeProbe.value(includeDirKey);
2596             }
2597 
2598             public Bldr.CMakeProbe cmakeProbe;
2599 
2600             @Override
2601             public void accept(Bldr.CMakeProbe cmakeProbe) {
2602                 this.cmakeProbe = cmakeProbe;
2603             }
2604 
2605 
2606             @Override
2607             public void inversionOfControl(JExtractBuilder jextractBuilder) {
2608  jextractBuilder.os(mac -> jextractBuilder
2609                                 .compile_flag("-F"
2610                                         + appLibFrameworks() + "/System/library/Frameworks")
2611                                 .library(mac.frameworkLibrary("OpenCL"))
2612                                 .header(Path.of(includeDir()).resolve("Headers/opencl.h")),
2613                         linux -> {
2614                             throw new IllegalStateException("Linux not handled yet");
2615                         });
2616             }
2617         }
2618 
2619         public static final class OpenGL extends Capability implements CMakeProbeable, Jextractable {
2620             public static String glutIncludeDirKey = "CMAKE_GLUT_INCLUDE_DIR";
2621             public static String openGLIncludeDirKey = "CMAKE_OPENGL_INCLUDE_DIR";
2622             public static String libKey = "CMAKE_OPENGL_LIBRARY";
2623             public static String osxSysroot = "CMAKE_OSX_SYSROOT";
2624 
2625             public OpenGL() {
2626                 super("OpenGL");
2627             }
2628 
2629             public static OpenGL of() {
2630                 return new OpenGL();
2631             }
2632 
2633             @Override
2634             public boolean available() {
2635                 return cmakeProbe.hasKey(openGLIncludeDirKey);
2636             }
2637 
2638             public DirEntry openglIncludeDir() {
2639                 return DirEntry.of(Path.of(cmakeProbe.value(openGLIncludeDirKey)) + "/Headers");
2640             }
2641 
2642             public DirEntry glutIncludeDir() {
2643                 return DirEntry.of(cmakeProbe.value(osxSysroot)+"/System/Library/Frameworks/GLUT.framework/Headers");
2644             }
2645 
2646             public String appLibFrameworks() {
2647                 return cmakeProbe.value(osxSysroot);
2648             }
2649 
2650             public String lib() {
2651                 return cmakeProbe.value(libKey);
2652             }
2653 
2654             public Path lib(String frameworkName) {
2655                 return Path.of(cmakeProbe.value(libKey).split(";")[0]).resolve(frameworkName + ".framework/" + frameworkName);
2656             }
2657 
2658             @Override
2659             public String cmakeStanza() {
2660                 return
2661                         """
2662                                 find_package(OpenGL)
2663                                 if(OPENGL_FOUND)
2664                                     if (APPLE)
2665                                        set(OPENGL_FRAMEWORK "-framework OpenGL")
2666                                     else()
2667                                        set(OPENCL_LIB "OpenCL")
2668                                     endif()
2669                                 else()
2670                                     message("NO OPENGL FOUND")
2671                                 endif()
2672                                 """;
2673             }
2674 
2675             public Bldr.CMakeProbe cmakeProbe;
2676 
2677             @Override
2678             public void accept(Bldr.CMakeProbe cmakeProbe) {
2679 
2680                 this.cmakeProbe = cmakeProbe;
2681 /*
2682                 cmakeProbe.varMap.forEach((k, v) -> {
2683                     if (k.toUpperCase().contains("OPENGL")) {
2684                         println(k);
2685                     }
2686                     if (k.toUpperCase().contains("GLUT")) {
2687                         println(k);
2688                     }
2689                 }); */
2690 
2691             }
2692             @Override public void inversionOfControl(JExtractBuilder jextractBuilder){
2693                 jextractBuilder
2694                         .os(mac -> jextractBuilder
2695                                 .compile_flag("-F"
2696                                         + appLibFrameworks() + "/System/library/Frameworks")
2697                                 .library(mac.frameworkLibrary("OpenGL"))
2698                                 .library(mac.frameworkLibrary("GLUT"))
2699                                 .header(glutIncludeDir().dir("glut.h").path()),
2700                         linux -> {
2701                             throw new IllegalStateException("Linux not handled yet");
2702                         }
2703                 );
2704             }
2705         }
2706 
2707         public static final class HIP extends Capability implements CMakeProbeable, Jextractable {
2708             public HIP() {
2709                 super("HIP");
2710             }
2711 
2712             public static HIP of() {
2713                 return new HIP();
2714             }
2715 
2716             @Override
2717             public boolean available() {
2718                 return false;
2719             }
2720 
2721             @Override
2722             public String cmakeStanza() {
2723                 return
2724                         """
2725                                 find_package(HIP)
2726                                 if(HIP_FOUND)
2727 
2728                                 else()
2729                                     message("NO HIP FOUND")
2730                                 endif()
2731                                 """;
2732             }
2733 
2734             public Bldr.CMakeProbe cmakeProbe;
2735 
2736             @Override
2737             public void accept(Bldr.CMakeProbe cmakeProbe) {
2738 
2739                 this.cmakeProbe = cmakeProbe;
2740             }
2741 
2742             @Override
2743             public void inversionOfControl(JExtractBuilder jextractBuilder) {
2744 
2745             }
2746         }
2747 
2748         public static final class CUDA extends Capability implements CMakeProbeable, Jextractable {
2749             public static String sdkRootDirKey = "CMAKE_CUDA_SDK_ROOT_DIR";
2750             public static String sdkRootDirNotFoundValue = "CUDA_SDK_ROOT_DIR-NOTFOUND";
2751 
2752             public CUDA() {
2753                 super("CUDA");
2754             }
2755 
2756             public static CUDA of() {
2757                 return new CUDA();
2758             }
2759 
2760             @Override
2761             public boolean available() {
2762                 return cmakeProbe.hasKey(sdkRootDirKey) && !cmakeProbe.value(sdkRootDirKey).equals(sdkRootDirNotFoundValue);
2763             }
2764 
2765             @Override
2766             public String cmakeStanza() {
2767                 return
2768                         """
2769                                 find_package(CUDAToolkit)
2770                                 if(CUDAToolkit_FOUND)
2771                                     set(CUDA_FOUND true)
2772                                     set(CUDA_INCLUDE_DIR ${CUDAToolkit_INCLUDE_DIR})
2773                                     set(CUDA_LIBRARY_DIR ${CUDAToolkit_LIBRARY_DIR})
2774                                     set(CUDA_LIBRARIES "-lcudart -lcuda")
2775                                 else()
2776                                     message("NO CUDA FOUND")
2777                                 endif()
2778                                 """;
2779             }
2780 
2781             public Bldr.CMakeProbe cmakeProbe;
2782 
2783             @Override
2784             public void accept(Bldr.CMakeProbe cmakeProbe) {
2785                 this.cmakeProbe = cmakeProbe;
2786             }
2787 
2788             @Override
2789             public void inversionOfControl(JExtractBuilder jextractBuilder) {
2790 
2791             }
2792         }
2793 
2794         public static final class JExtract extends Capability implements Executable {
2795             public JExtractExecutable executable;
2796 
2797             JExtract() {
2798                 super("JExtract");
2799                 var optionalExe = fromPATH("jextract");
2800                 if (optionalExe.isEmpty()) {
2801                     //  println("jextract not in path");
2802                 } else {
2803                     executable = JExtractExecutable.of(optionalExe.get());
2804                 }
2805 
2806             }
2807 
2808             JExtract(Path executable) {
2809                 super("JExtract");
2810                 this.executable = JExtractExecutable.of(executable);
2811             }
2812 
2813             @Override
2814             public boolean available() {
2815                 return executable != null && executable.exists();
2816             }
2817 
2818             public static JExtract of() {
2819                 return new JExtract();
2820             }
2821 
2822             public static JExtract of(Path executable) {
2823                 return new JExtract(executable);
2824             }
2825 
2826 
2827             @Override
2828             public Path path() {
2829                 return executable.path;
2830             }
2831         }
2832 
2833         public static final class CMake extends Capability implements Executable {
2834             public JExtractExecutable executable;
2835             public Bldr.CMakeProbe cmakeProbe;
2836 
2837             CMake() {
2838                 super("CMake");
2839                 var optionalExe = fromPATH("cmake");
2840                 if (optionalExe.isEmpty()) {
2841                     println("cmake not in path");
2842                 } else {
2843                     executable = JExtractExecutable.of(optionalExe.get());
2844                 }
2845             }
2846 
2847             @Override
2848             public boolean available() {
2849                 return executable != null && executable.exists();
2850             }
2851 
2852             public static CMake of() {
2853                 return new CMake();
2854             }
2855 
2856             public void probe(BuildDir buildDir, Capabilities capabilities) {
2857                 this.cmakeProbe = new Bldr.CMakeProbe(buildDir, capabilities);
2858             }
2859 
2860             @Override
2861             public Path path() {
2862                 return executable.path();
2863             }
2864         }
2865 
2866     }
2867 
2868     public record Regex(Pattern pattern) {
2869         Regex(String regex) {
2870             this(Pattern.compile(regex));
2871         }
2872 
2873         public static Regex of(String regexString) {
2874             return new Regex(regexString);
2875         }
2876 
2877         boolean matches(String text, Consumer<Matcher> matcherConsumer) {
2878             if (pattern().matcher(text) instanceof Matcher matcher && matcher.matches()) {
2879                 matcherConsumer.accept(matcher);
2880                 return true;
2881             } else {
2882                 return false;
2883             }
2884         }
2885     }
2886 
2887     public static class XMLNode {
2888         Element element;
2889         List<XMLNode> children = new ArrayList<>();
2890         Map<String, String> attrMap = new HashMap<>();
2891 
2892         public static class AbstractXMLBuilder<T extends AbstractXMLBuilder<T>> {
2893             final public Element element;
2894 
2895             @SuppressWarnings("unchecked")
2896             public T self() {
2897                 return (T) this;
2898             }
2899 
2900             public T attr(String name, String value) {
2901                 element.setAttribute(name, value);
2902                 return self();
2903             }
2904 
2905             public T attr(URI uri, String name, String value) {
2906                 element.setAttributeNS(uri.toString(), name, value);
2907                 return self();
2908             }
2909 
2910             public T element(String name, Function<Element, T> factory, Consumer<T> xmlBuilderConsumer) {
2911                 var node = element.getOwnerDocument().createElement(name);
2912                 element.appendChild(node);
2913                 var builder = factory.apply(node);
2914                 xmlBuilderConsumer.accept(builder);
2915                 return self();
2916             }
2917 
2918             public T element(
2919                     URI uri, String name, Function<Element, T> factory, Consumer<T> xmlBuilderConsumer) {
2920                 var node = element.getOwnerDocument().createElementNS(uri.toString(), name);
2921                 element.appendChild(node);
2922                 var builder = factory.apply(node);
2923                 xmlBuilderConsumer.accept(builder);
2924                 return self();
2925             }
2926 
2927             AbstractXMLBuilder(Element element) {
2928                 this.element = element;
2929             }
2930 
2931             public T text(String thisText) {
2932                 var node = element.getOwnerDocument().createTextNode(thisText);
2933                 element.appendChild(node);
2934                 return self();
2935             }
2936 
2937             public T comment(String thisComment) {
2938                 var node = element.getOwnerDocument().createComment(thisComment);
2939                 element.appendChild(node);
2940                 return self();
2941             }
2942 
2943             <L> T forEach(List<L> list, BiConsumer<T, L> biConsumer) {
2944                 list.forEach(l -> biConsumer.accept(self(), l));
2945                 return self();
2946             }
2947 
2948             <L> T forEach(Stream<L> stream, BiConsumer<T, L> biConsumer) {
2949                 stream.forEach(l -> biConsumer.accept(self(), l));
2950                 return self();
2951             }
2952 
2953             <L> T forEach(Stream<L> stream, Consumer<L> consumer) {
2954                 stream.forEach(consumer);
2955                 return self();
2956             }
2957 
2958             protected T then(Consumer<T> xmlBuilderConsumer) {
2959                 xmlBuilderConsumer.accept(self());
2960                 return self();
2961             }
2962         }
2963 
2964         public static class PomXmlBuilder extends AbstractXMLBuilder<PomXmlBuilder> {
2965             PomXmlBuilder(Element element) {
2966                 super(element);
2967             }
2968 
2969             public PomXmlBuilder element(String name, Consumer<PomXmlBuilder> xmlBuilderConsumer) {
2970                 return element(name, PomXmlBuilder::new, xmlBuilderConsumer);
2971             }
2972 
2973             public PomXmlBuilder element(URI uri, String name, Consumer<PomXmlBuilder> xmlBuilderConsumer) {
2974                 return element(uri, name, PomXmlBuilder::new, xmlBuilderConsumer);
2975             }
2976 
2977             public PomXmlBuilder modelVersion(String s) {
2978                 return element("modelVersion", $ -> $.text(s));
2979             }
2980 
2981             public PomXmlBuilder pom(String groupId, String artifactId, String version) {
2982                 return modelVersion("4.0.0").packaging("pom").ref(groupId, artifactId, version);
2983             }
2984 
2985             public PomXmlBuilder jar(String groupId, String artifactId, String version) {
2986                 return modelVersion("4.0.0").packaging("jar").ref(groupId, artifactId, version);
2987             }
2988 
2989             public PomXmlBuilder groupId(String s) {
2990                 return element("groupId", $ -> $.text(s));
2991             }
2992 
2993             public PomXmlBuilder artifactId(String s) {
2994                 return element("artifactId", $ -> $.text(s));
2995             }
2996 
2997             public PomXmlBuilder packaging(String s) {
2998                 return element("packaging", $ -> $.text(s));
2999             }
3000 
3001             public PomXmlBuilder version(String s) {
3002                 return element("version", $ -> $.text(s));
3003             }
3004 
3005             public PomXmlBuilder build(Consumer<PomXmlBuilder> pomXmlBuilderConsumer) {
3006                 return element("build", pomXmlBuilderConsumer);
3007             }
3008 
3009             public PomXmlBuilder plugins(Consumer<PomXmlBuilder> pomXmlBuilderConsumer) {
3010                 return element("plugins", pomXmlBuilderConsumer);
3011             }
3012 
3013             public PomXmlBuilder plugin(
3014                     String groupId,
3015                     String artifactId,
3016                     String version,
3017                     Consumer<PomXmlBuilder> pomXmlBuilderConsumer) {
3018                 return element(
3019                         "plugin", $ -> $.ref(groupId, artifactId, version).then(pomXmlBuilderConsumer));
3020             }
3021 
3022             public PomXmlBuilder antPlugin(Consumer<PomXmlBuilder> pomXmlBuilderConsumer) {
3023                 return plugin(
3024                         "org.apache.maven.plugins",
3025                         "maven-antrun-plugin",
3026                         "1.8",
3027                         pomXmlBuilderConsumer);
3028             }
3029 
3030             public PomXmlBuilder surefirePlugin(Consumer<PomXmlBuilder> pomXmlBuilderConsumer) {
3031                 return plugin(
3032                         "org.apache.maven.plugins",
3033                         "maven-surefire-plugin",
3034                         "3.1.2",
3035                         pomXmlBuilderConsumer);
3036             }
3037 
3038             public PomXmlBuilder compilerPlugin(
3039                     Consumer<PomXmlBuilder> pomXmlBuilderConsumer) {
3040                 return plugin(
3041                         "org.apache.maven.plugins",
3042                         "maven-compiler-plugin",
3043                         "3.11.0", pomXmlBuilderConsumer
3044                 );
3045             }
3046 
3047             public PomXmlBuilder execPlugin(Consumer<PomXmlBuilder> pomXmlBuilderConsumer) {
3048                 return plugin("org.codehaus.mojo", "exec-maven-plugin", "3.1.0", pomXmlBuilderConsumer);
3049             }
3050 
3051 
3052             public PomXmlBuilder plugin(
3053                     String groupId, String artifactId, Consumer<PomXmlBuilder> pomXmlBuilderConsumer) {
3054                 return element("plugin", $ -> $.groupIdArtifactId(groupId, artifactId).then(pomXmlBuilderConsumer));
3055             }
3056 
3057             public PomXmlBuilder plugin(Consumer<PomXmlBuilder> pomXmlBuilderConsumer) {
3058                 return element("plugin", pomXmlBuilderConsumer);
3059             }
3060 
3061             public PomXmlBuilder parent(String groupId, String artifactId, String version) {
3062                 return parent(parent -> parent.ref(groupId, artifactId, version));
3063             }
3064 
3065             public PomXmlBuilder parent(Consumer<PomXmlBuilder> pomXmlBuilderConsumer) {
3066                 return element("parent", pomXmlBuilderConsumer);
3067             }
3068 
3069             public PomXmlBuilder pluginManagement(Consumer<PomXmlBuilder> pomXmlBuilderConsumer) {
3070                 return element("pluginManagement", pomXmlBuilderConsumer);
3071             }
3072 
3073             public PomXmlBuilder file(Consumer<PomXmlBuilder> pomXmlBuilderConsumer) {
3074                 return element("file", pomXmlBuilderConsumer);
3075             }
3076 
3077             public PomXmlBuilder activation(Consumer<PomXmlBuilder> pomXmlBuilderConsumer) {
3078                 return element("activation", pomXmlBuilderConsumer);
3079             }
3080 
3081             public PomXmlBuilder profiles(Consumer<PomXmlBuilder> pomXmlBuilderConsumer) {
3082                 return element("profiles", pomXmlBuilderConsumer);
3083             }
3084 
3085             public PomXmlBuilder profile(Consumer<PomXmlBuilder> pomXmlBuilderConsumer) {
3086                 return element("profile", pomXmlBuilderConsumer);
3087             }
3088 
3089             public PomXmlBuilder arguments(Consumer<PomXmlBuilder> pomXmlBuilderConsumer) {
3090                 return element("arguments", pomXmlBuilderConsumer);
3091             }
3092 
3093             public PomXmlBuilder executions(Consumer<PomXmlBuilder> pomXmlBuilderConsumer) {
3094                 return element("executions", pomXmlBuilderConsumer);
3095             }
3096 
3097             public PomXmlBuilder execution(Consumer<PomXmlBuilder> pomXmlBuilderConsumer) {
3098                 return element("execution", pomXmlBuilderConsumer);
3099             }
3100 
3101             public PomXmlBuilder execIdPhaseConf(
3102                     String id, String phase, Consumer<PomXmlBuilder> pomXmlBuilderConsumer) {
3103                 return execution(execution -> execution.id(id).phase(phase).goals(gs -> gs.goal("exec")).configuration(pomXmlBuilderConsumer));
3104             }
3105 
3106             public PomXmlBuilder exec(
3107                     String phase, String executable, Consumer<PomXmlBuilder> pomXmlBuilderConsumer) {
3108                 return execIdPhaseConf(
3109                         executable + "-" + phase,
3110                         phase,
3111                         conf -> conf.executable(executable).arguments(pomXmlBuilderConsumer));
3112             }
3113 
3114             public PomXmlBuilder cmake(
3115                     String id, String phase, Consumer<PomXmlBuilder> pomXmlBuilderConsumer) {
3116                 return execIdPhaseConf(
3117                         id, phase, conf -> conf.executable("cmake").arguments(pomXmlBuilderConsumer));
3118             }
3119 
3120             public PomXmlBuilder cmake(String id, String phase, String... args) {
3121                 return execIdPhaseConf(
3122                         id,
3123                         phase,
3124                         conf ->
3125                                 conf.executable("cmake")
3126                                         .arguments(arguments -> arguments.forEach(Stream.of(args), arguments::argument)));
3127             }
3128 
3129             public PomXmlBuilder jextract(String id, String phase, String... args) {
3130                 return execIdPhaseConf(
3131                         id,
3132                         phase,
3133                         conf ->
3134                                 conf.executable("jextract")
3135                                         .arguments(arguments -> arguments.forEach(Stream.of(args), arguments::argument)));
3136             }
3137 
3138             public PomXmlBuilder ant(
3139                     String id, String phase, String goal, Consumer<PomXmlBuilder> pomXmlBuilderConsumer) {
3140                 return execution(execution -> execution
3141                         .id(id)
3142                         .phase(phase)
3143                         .goals(gs -> gs.goal(goal))
3144                         .configuration(configuration -> configuration.target(pomXmlBuilderConsumer)));
3145             }
3146 
3147             public PomXmlBuilder goals(Consumer<PomXmlBuilder> pomXmlBuilderConsumer) {
3148                 return element("goals", pomXmlBuilderConsumer);
3149             }
3150 
3151             public PomXmlBuilder target(Consumer<PomXmlBuilder> pomXmlBuilderConsumer) {
3152                 return element("target", pomXmlBuilderConsumer);
3153             }
3154 
3155             public PomXmlBuilder configuration(Consumer<PomXmlBuilder> pomXmlBuilderConsumer) {
3156                 return element("configuration", pomXmlBuilderConsumer);
3157             }
3158 
3159             public PomXmlBuilder compilerArgs(Consumer<PomXmlBuilder> pomXmlBuilderConsumer) {
3160                 return element("compilerArgs", pomXmlBuilderConsumer);
3161             }
3162 
3163             public PomXmlBuilder compilerArgs(String... args) {
3164                 return element("compilerArgs", $ -> $.forEach(Stream.of(args), $::arg));
3165             }
3166 
3167             public PomXmlBuilder properties(Consumer<PomXmlBuilder> pomXmlBuilderConsumer) {
3168                 return element("properties", pomXmlBuilderConsumer);
3169             }
3170 
3171             public PomXmlBuilder dependencies(Consumer<PomXmlBuilder> pomXmlBuilderConsumer) {
3172                 return element("dependencies", pomXmlBuilderConsumer);
3173             }
3174 
3175             public PomXmlBuilder dependsOn(String groupId, String artifactId, String version) {
3176                 return element("dependencies", $ -> $.dependency(groupId, artifactId, version));
3177             }
3178 
3179             public PomXmlBuilder dependsOn(String groupId, String artifactId, String version, String phase) {
3180                 return element("dependencies", $ -> $.dependency(groupId, artifactId, version, phase));
3181             }
3182 
3183             public PomXmlBuilder dependency(String groupId, String artifactId, String version) {
3184                 return dependency($ -> $.ref(groupId, artifactId, version));
3185             }
3186 
3187             public PomXmlBuilder dependency(
3188                     String groupId, String artifactId, String version, String scope) {
3189                 return dependency($ -> $.ref(groupId, artifactId, version).scope(scope));
3190             }
3191 
3192             public PomXmlBuilder dependency(Consumer<PomXmlBuilder> pomXmlBuilderConsumer) {
3193                 return element("dependency", pomXmlBuilderConsumer);
3194             }
3195 
3196             public PomXmlBuilder modules(Consumer<PomXmlBuilder> pomXmlBuilderConsumer) {
3197                 return element("modules", pomXmlBuilderConsumer);
3198             }
3199 
3200             public PomXmlBuilder modules(List<String> modules) {
3201                 return element("modules", $ -> $.forEach(modules.stream(), $::module));
3202             }
3203 
3204             public PomXmlBuilder modules(String... modules) {
3205                 return modules(List.of(modules));
3206             }
3207 
3208             public PomXmlBuilder module(String name) {
3209                 return element("module", $ -> $.text(name));
3210             }
3211 
3212             public PomXmlBuilder property(String name, String value) {
3213                 return element(name, $ -> $.text(value));
3214             }
3215 
3216             public PomXmlBuilder antproperty(String name, String value) {
3217                 return element("property", $ -> $.attr("name", name).attr("value", value));
3218             }
3219 
3220             public PomXmlBuilder scope(String s) {
3221                 return element("scope", $ -> $.text(s));
3222             }
3223 
3224             public PomXmlBuilder phase(String s) {
3225                 return element("phase", $ -> $.text(s));
3226             }
3227 
3228             public PomXmlBuilder argument(String s) {
3229                 return element("argument", $ -> $.text(s));
3230             }
3231 
3232             public PomXmlBuilder goal(String s) {
3233                 return element("goal", $ -> $.text(s));
3234             }
3235 
3236             public PomXmlBuilder copy(String file, String toDir) {
3237                 return element("copy", $ -> $.attr("file", file).attr("toDir", toDir));
3238             }
3239 
3240             public PomXmlBuilder antjar(String basedir, String include, String destfile) {
3241                 return element("jar", $ -> $.attr("basedir", basedir).attr("includes", include + "/**").attr("destfile", destfile));
3242             }
3243 
3244             public PomXmlBuilder echo(String message) {
3245                 return element("echo", $ -> $.attr("message", message));
3246             }
3247 
3248             public PomXmlBuilder echo(String filename, String message) {
3249                 return element("echo", $ -> $.attr("message", message).attr("file", filename));
3250             }
3251 
3252             public PomXmlBuilder mkdir(String dirName) {
3253                 return element("mkdir", $ -> $.attr("dir", dirName));
3254             }
3255 
3256             public PomXmlBuilder groupIdArtifactId(String groupId, String artifactId) {
3257                 return groupId(groupId).artifactId(artifactId);
3258             }
3259 
3260             public PomXmlBuilder ref(String groupId, String artifactId, String version) {
3261                 return groupIdArtifactId(groupId, artifactId).version(version);
3262             }
3263 
3264             public PomXmlBuilder skip(String string) {
3265                 return element("skip", $ -> $.text(string));
3266             }
3267 
3268             public PomXmlBuilder id(String s) {
3269                 return element("id", $ -> $.text(s));
3270             }
3271 
3272             public PomXmlBuilder arg(String s) {
3273                 return element("arg", $ -> $.text(s));
3274             }
3275 
3276             public PomXmlBuilder argLine(String s) {
3277                 return element("argLine", $ -> $.text(s));
3278             }
3279 
3280             public PomXmlBuilder source(String s) {
3281                 return element("source", $ -> $.text(s));
3282             }
3283 
3284             public PomXmlBuilder target(String s) {
3285                 return element("target", $ -> $.text(s));
3286             }
3287 
3288             public PomXmlBuilder showWarnings(String s) {
3289                 return element("showWarnings", $ -> $.text(s));
3290             }
3291 
3292             public PomXmlBuilder showDeprecation(String s) {
3293                 return element("showDeprecation", $ -> $.text(s));
3294             }
3295 
3296             public PomXmlBuilder failOnError(String s) {
3297                 return element("failOnError", $ -> $.text(s));
3298             }
3299 
3300             public PomXmlBuilder exists(String s) {
3301                 return element("exists", $ -> $.text(s));
3302             }
3303 
3304             public PomXmlBuilder activeByDefault(String s) {
3305                 return element("activeByDefault", $ -> $.text(s));
3306             }
3307 
3308             public PomXmlBuilder executable(String s) {
3309                 return element("executable", $ -> $.text(s));
3310             }
3311 
3312             public PomXmlBuilder workingDirectory(String s) {
3313                 return element("workingDirectory", $ -> $.text(s));
3314             }
3315         }
3316 
3317         public static class ImlBuilder extends AbstractXMLBuilder<ImlBuilder> {
3318 
3319             ImlBuilder(Element element) {
3320                 super(element);
3321             }
3322 
3323             public ImlBuilder element(String name, Consumer<ImlBuilder> xmlBuilderConsumer) {
3324                 return element(name, ImlBuilder::new, xmlBuilderConsumer);
3325             }
3326 
3327             public ImlBuilder element(URI uri, String name, Consumer<ImlBuilder> xmlBuilderConsumer) {
3328                 return element(uri, name, ImlBuilder::new, xmlBuilderConsumer);
3329             }
3330 
3331             public ImlBuilder modelVersion(String s) {
3332                 return element("modelVersion", $ -> $.text(s));
3333             }
3334 
3335             public ImlBuilder groupId(String s) {
3336                 return element("groupId", $ -> $.text(s));
3337             }
3338 
3339             public ImlBuilder artifactId(String s) {
3340                 return element("artifactId", $ -> $.text(s));
3341             }
3342 
3343             public ImlBuilder packaging(String s) {
3344                 return element("packaging", $ -> $.text(s));
3345             }
3346 
3347             public ImlBuilder version(String s) {
3348                 return element("version", $ -> $.text(s));
3349             }
3350 
3351             public ImlBuilder build(Consumer<ImlBuilder> pomXmlBuilderConsumer) {
3352                 return element("build", pomXmlBuilderConsumer);
3353             }
3354 
3355             public ImlBuilder plugins(Consumer<ImlBuilder> pomXmlBuilderConsumer) {
3356                 return element("plugins", pomXmlBuilderConsumer);
3357             }
3358 
3359             public ImlBuilder plugin(
3360                     String groupId,
3361                     String artifactId,
3362                     String version,
3363                     Consumer<ImlBuilder> pomXmlBuilderConsumer) {
3364                 return element(
3365                         "plugin",
3366                         $ ->
3367                                 $.groupIdArtifactIdVersion(groupId, artifactId, version).then(pomXmlBuilderConsumer));
3368             }
3369 
3370             public ImlBuilder plugin(
3371                     String groupId, String artifactId, Consumer<ImlBuilder> pomXmlBuilderConsumer) {
3372                 return element(
3373                         "plugin", $ -> $.groupIdArtifactId(groupId, artifactId).then(pomXmlBuilderConsumer));
3374             }
3375 
3376             public ImlBuilder plugin(Consumer<ImlBuilder> pomXmlBuilderConsumer) {
3377                 return element("plugin", pomXmlBuilderConsumer);
3378             }
3379 
3380             public ImlBuilder parent(String groupId, String artifactId, String version) {
3381                 return parent(parent -> parent.groupIdArtifactIdVersion(groupId, artifactId, version));
3382             }
3383 
3384             public ImlBuilder parent(Consumer<ImlBuilder> pomXmlBuilderConsumer) {
3385                 return element("parent", pomXmlBuilderConsumer);
3386             }
3387 
3388             public ImlBuilder pluginManagement(Consumer<ImlBuilder> pomXmlBuilderConsumer) {
3389                 return element("pluginManagement", pomXmlBuilderConsumer);
3390             }
3391 
3392             public ImlBuilder file(Consumer<ImlBuilder> pomXmlBuilderConsumer) {
3393                 return element("file", pomXmlBuilderConsumer);
3394             }
3395 
3396             public ImlBuilder activation(Consumer<ImlBuilder> pomXmlBuilderConsumer) {
3397                 return element("activation", pomXmlBuilderConsumer);
3398             }
3399 
3400             public ImlBuilder profiles(Consumer<ImlBuilder> pomXmlBuilderConsumer) {
3401                 return element("profiles", pomXmlBuilderConsumer);
3402             }
3403 
3404             public ImlBuilder profile(Consumer<ImlBuilder> pomXmlBuilderConsumer) {
3405                 return element("profile", pomXmlBuilderConsumer);
3406             }
3407 
3408             public ImlBuilder arguments(Consumer<ImlBuilder> pomXmlBuilderConsumer) {
3409                 return element("arguments", pomXmlBuilderConsumer);
3410             }
3411 
3412             public ImlBuilder executions(Consumer<ImlBuilder> pomXmlBuilderConsumer) {
3413                 return element("executions", pomXmlBuilderConsumer);
3414             }
3415 
3416             public ImlBuilder execution(Consumer<ImlBuilder> pomXmlBuilderConsumer) {
3417                 return element("execution", pomXmlBuilderConsumer);
3418             }
3419 
3420             public ImlBuilder goals(Consumer<ImlBuilder> pomXmlBuilderConsumer) {
3421                 return element("goals", pomXmlBuilderConsumer);
3422             }
3423 
3424             public ImlBuilder target(Consumer<ImlBuilder> pomXmlBuilderConsumer) {
3425                 return element("target", pomXmlBuilderConsumer);
3426             }
3427 
3428             public ImlBuilder configuration(Consumer<ImlBuilder> pomXmlBuilderConsumer) {
3429                 return element("configuration", pomXmlBuilderConsumer);
3430             }
3431 
3432             public ImlBuilder compilerArgs(Consumer<ImlBuilder> pomXmlBuilderConsumer) {
3433                 return element("compilerArgs", pomXmlBuilderConsumer);
3434             }
3435 
3436             public ImlBuilder properties(Consumer<ImlBuilder> pomXmlBuilderConsumer) {
3437                 return element("properties", pomXmlBuilderConsumer);
3438             }
3439 
3440             public ImlBuilder dependencies(Consumer<ImlBuilder> pomXmlBuilderConsumer) {
3441                 return element("dependencies", pomXmlBuilderConsumer);
3442             }
3443 
3444             public ImlBuilder dependency(String groupId, String artifactId, String version) {
3445                 return dependency($ -> $.groupIdArtifactIdVersion(groupId, artifactId, version));
3446             }
3447 
3448             public ImlBuilder dependency(String groupId, String artifactId, String version, String scope) {
3449                 return dependency($ -> $.groupIdArtifactIdVersion(groupId, artifactId, version).scope(scope));
3450             }
3451 
3452             public ImlBuilder dependency(Consumer<ImlBuilder> pomXmlBuilderConsumer) {
3453                 return element("dependency", pomXmlBuilderConsumer);
3454             }
3455 
3456             public ImlBuilder modules(Consumer<ImlBuilder> pomXmlBuilderConsumer) {
3457                 return element("modules", pomXmlBuilderConsumer);
3458             }
3459 
3460             public ImlBuilder module(String name) {
3461                 return element("module", $ -> $.text(name));
3462             }
3463 
3464             public ImlBuilder property(String name, String value) {
3465                 return element(name, $ -> $.text(value));
3466             }
3467 
3468             public ImlBuilder scope(String s) {
3469                 return element("scope", $ -> $.text(s));
3470             }
3471 
3472             public ImlBuilder phase(String s) {
3473                 return element("phase", $ -> $.text(s));
3474             }
3475 
3476             public ImlBuilder argument(String s) {
3477                 return element("argument", $ -> $.text(s));
3478             }
3479 
3480             public ImlBuilder goal(String s) {
3481                 return element("goal", $ -> $.text(s));
3482             }
3483 
3484             public ImlBuilder copy(String file, String toDir) {
3485                 return element("copy", $ -> $.attr("file", file).attr("toDir", toDir));
3486             }
3487 
3488             public ImlBuilder groupIdArtifactId(String groupId, String artifactId) {
3489                 return groupId(groupId).artifactId(artifactId);
3490             }
3491 
3492             public ImlBuilder groupIdArtifactIdVersion(String groupId, String artifactId, String version) {
3493                 return groupIdArtifactId(groupId, artifactId).version(version);
3494             }
3495 
3496             public ImlBuilder skip(String string) {
3497                 return element("skip", $ -> $.text(string));
3498             }
3499 
3500             public ImlBuilder id(String s) {
3501                 return element("id", $ -> $.text(s));
3502             }
3503 
3504             public ImlBuilder arg(String s) {
3505                 return element("arg", $ -> $.text(s));
3506             }
3507 
3508             public ImlBuilder argLine(String s) {
3509                 return element("argLine", $ -> $.text(s));
3510             }
3511 
3512             public ImlBuilder source(String s) {
3513                 return element("source", $ -> $.text(s));
3514             }
3515 
3516             public ImlBuilder target(String s) {
3517                 return element("target", $ -> $.text(s));
3518             }
3519 
3520             public ImlBuilder showWarnings(String s) {
3521                 return element("showWarnings", $ -> $.text(s));
3522             }
3523 
3524             public ImlBuilder showDeprecation(String s) {
3525                 return element("showDeprecation", $ -> $.text(s));
3526             }
3527 
3528             public ImlBuilder failOnError(String s) {
3529                 return element("failOnError", $ -> $.text(s));
3530             }
3531 
3532             public ImlBuilder exists(String s) {
3533                 return element("exists", $ -> $.text(s));
3534             }
3535 
3536             public ImlBuilder activeByDefault(String s) {
3537                 return element("activeByDefault", $ -> $.text(s));
3538             }
3539 
3540             public ImlBuilder executable(String s) {
3541                 return element("executable", $ -> $.text(s));
3542             }
3543         }
3544 
3545         public static class XMLBuilder extends AbstractXMLBuilder<XMLBuilder> {
3546             XMLBuilder(Element element) {
3547                 super(element);
3548             }
3549 
3550             public XMLBuilder element(String name, Consumer<XMLBuilder> xmlBuilderConsumer) {
3551                 return element(name, XMLBuilder::new, xmlBuilderConsumer);
3552             }
3553 
3554             public XMLBuilder element(URI uri, String name, Consumer<XMLBuilder> xmlBuilderConsumer) {
3555                 return element(uri, name, XMLBuilder::new, xmlBuilderConsumer);
3556             }
3557         }
3558 
3559         static XMLNode create(String nodeName, Consumer<XMLBuilder> xmlBuilderConsumer) {
3560 
3561             try {
3562                 var doc =
3563                         DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument();
3564                 var element = doc.createElement(nodeName);
3565                 doc.appendChild(element);
3566                 XMLBuilder xmlBuilder = new XMLBuilder(element);
3567                 xmlBuilderConsumer.accept(xmlBuilder);
3568                 return new XMLNode(element);
3569             } catch (ParserConfigurationException e) {
3570                 throw new RuntimeException(e);
3571             }
3572         }
3573 
3574         static XMLNode createIml(String commentText, Consumer<ImlBuilder> imlBuilderConsumer) {
3575             try {
3576                 var doc =
3577                         DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument();
3578                 var uri1 = URI.create("http://maven.apache.org/POM/4.0.0");
3579                 var uri2 = URI.create("http://www.w3.org/2001/XMLSchema-instance");
3580                 var uri3 = URI.create("http://maven.apache.org/xsd/maven-4.0.0.xsd");
3581                 var comment = doc.createComment(commentText);
3582                 doc.appendChild(comment);
3583                 var element = doc.createElementNS(uri1.toString(), "project");
3584                 doc.appendChild(element);
3585                 element.setAttributeNS(uri2.toString(), "xsi:schemaLocation", uri1 + " " + uri3);
3586                 ImlBuilder imlBuilder = new ImlBuilder(element);
3587                 imlBuilderConsumer.accept(imlBuilder);
3588                 return new XMLNode(element);
3589             } catch (ParserConfigurationException e) {
3590                 throw new RuntimeException(e);
3591             }
3592         }
3593 
3594         public static XMLNode createPom(
3595                 String commentText, Consumer<PomXmlBuilder> pomXmlBuilderConsumer) {
3596             try {
3597                 var doc =
3598                         DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument();
3599 
3600                 var uri1 = URI.create("http://maven.apache.org/POM/4.0.0");
3601                 var uri2 = URI.create("http://www.w3.org/2001/XMLSchema-instance");
3602                 var uri3 = URI.create("http://maven.apache.org/xsd/maven-4.0.0.xsd");
3603                 var comment = doc.createComment(commentText);
3604                 doc.appendChild(comment);
3605                 var element = doc.createElementNS(uri1.toString(), "project");
3606                 doc.appendChild(element);
3607                 element.setAttributeNS(uri2.toString(), "xsi:schemaLocation", uri1 + " " + uri3);
3608                 PomXmlBuilder pomXmlBuilder = new PomXmlBuilder(element);
3609                 pomXmlBuilderConsumer.accept(pomXmlBuilder);
3610                 return new XMLNode(element);
3611             } catch (ParserConfigurationException e) {
3612                 throw new RuntimeException(e);
3613             }
3614         }
3615 
3616         static XMLNode create(URI uri, String nodeName, Consumer<XMLBuilder> xmlBuilderConsumer) {
3617             try {
3618                 var doc =
3619                         DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument();
3620                 var element = doc.createElementNS(uri.toString(), nodeName);
3621                 doc.appendChild(element);
3622                 XMLBuilder xmlBuilder = new XMLBuilder(element);
3623                 xmlBuilderConsumer.accept(xmlBuilder);
3624                 return new XMLNode(element);
3625             } catch (ParserConfigurationException e) {
3626                 throw new RuntimeException(e);
3627             }
3628         }
3629 
3630         XMLNode(Element element) {
3631             this.element = element;
3632             this.element.normalize();
3633             NodeList nodeList = element.getChildNodes();
3634             for (int i = 0; i < nodeList.getLength(); i++) {
3635                 if (nodeList.item(i) instanceof Element e) {
3636                     this.children.add(new XMLNode(e));
3637                 }
3638             }
3639             for (int i = 0; i < element.getAttributes().getLength(); i++) {
3640                 if (element.getAttributes().item(i) instanceof Attr attr) {
3641                     this.attrMap.put(attr.getName(), attr.getValue());
3642                 }
3643             }
3644         }
3645 
3646         public boolean hasAttr(String name) {
3647             return attrMap.containsKey(name);
3648         }
3649 
3650         public String attr(String name) {
3651             return attrMap.get(name);
3652         }
3653 
3654         static Document parse(InputStream is) {
3655             try {
3656                 return DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(is);
3657             } catch (ParserConfigurationException | SAXException | IOException e) {
3658                 throw new RuntimeException(e);
3659             }
3660         }
3661 
3662         static Document parse(Path path) {
3663             try {
3664                 return parse(Files.newInputStream(path));
3665             } catch (IOException e) {
3666                 throw new RuntimeException(e);
3667             }
3668         }
3669 
3670         XMLNode(Path path) {
3671             this(parse(path).getDocumentElement());
3672         }
3673 
3674         XMLNode(File file) {
3675             this(parse(file.toPath()).getDocumentElement());
3676         }
3677 
3678         XMLNode(URL url) throws Throwable {
3679             this(parse(url.openStream()).getDocumentElement());
3680         }
3681 
3682         void write(StreamResult streamResult) throws Throwable {
3683             var transformer = TransformerFactory.newInstance().newTransformer();
3684             transformer.setOutputProperty(OutputKeys.INDENT, "yes");
3685             transformer.setOutputProperty(OutputKeys.METHOD, "xml");
3686             transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "no");
3687             transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "4");
3688             transformer.transform(new DOMSource(element.getOwnerDocument()), streamResult);
3689         }
3690 
3691         void write(File file) {
3692             try {
3693                 write(new StreamResult(file));
3694             } catch (Throwable t) {
3695                 throw new RuntimeException(t);
3696             }
3697         }
3698 
3699         public void write(XMLFile xmlFile) {
3700             try {
3701                 write(new StreamResult(xmlFile.path().toFile()));
3702             } catch (Throwable t) {
3703                 throw new RuntimeException(t);
3704             }
3705         }
3706 
3707         @Override
3708         public String toString() {
3709             var stringWriter = new StringWriter();
3710             try {
3711                 var transformer = TransformerFactory.newInstance().newTransformer();
3712                 transformer.setOutputProperty(OutputKeys.INDENT, "yes");
3713                 transformer.setOutputProperty(OutputKeys.METHOD, "xml");
3714                 transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "4");
3715                 transformer.transform(new DOMSource(element), new StreamResult(stringWriter));
3716                 return stringWriter.toString();
3717             } catch (Throwable e) {
3718                 throw new RuntimeException(e);
3719             }
3720         }
3721 
3722         XPathExpression xpath(String expression) {
3723             XPath xpath = XPathFactory.newInstance().newXPath();
3724             try {
3725                 return xpath.compile(expression);
3726             } catch (XPathExpressionException e) {
3727                 throw new RuntimeException(e);
3728             }
3729         }
3730 
3731         Node node(XPathExpression xPathExpression) {
3732             try {
3733                 return (Node) xPathExpression.evaluate(this.element, XPathConstants.NODE);
3734             } catch (XPathExpressionException e) {
3735                 throw new RuntimeException(e);
3736             }
3737         }
3738 
3739         Optional<Node> optionalNode(XPathExpression xPathExpression) {
3740             var nodes = nodes(xPathExpression).toList();
3741             return switch (nodes.size()) {
3742                 case 0 -> Optional.empty();
3743                 case 1 -> Optional.of(nodes.getFirst());
3744                 default -> throw new IllegalStateException("Expected 0 or 1 but got more");
3745             };
3746         }
3747 
3748         String str(XPathExpression xPathExpression) {
3749             try {
3750                 return (String) xPathExpression.evaluate(this.element, XPathConstants.STRING);
3751             } catch (XPathExpressionException e) {
3752                 throw new RuntimeException(e);
3753             }
3754         }
3755 
3756         String xpathQueryString(String xpathString) {
3757             try {
3758                 return (String) xpath(xpathString).evaluate(this.element, XPathConstants.STRING);
3759             } catch (XPathExpressionException e) {
3760                 throw new RuntimeException(e);
3761             }
3762         }
3763 
3764         NodeList nodeList(XPathExpression xPathExpression) {
3765             try {
3766                 return (NodeList) xPathExpression.evaluate(this.element, XPathConstants.NODESET);
3767             } catch (XPathExpressionException e) {
3768                 throw new RuntimeException(e);
3769             }
3770         }
3771 
3772         Stream<Node> nodes(XPathExpression xPathExpression) {
3773             var nodeList = nodeList(xPathExpression);
3774             List<Node> nodes = new ArrayList<>();
3775             for (int i = 0; i < nodeList.getLength(); i++) {
3776                 nodes.add(nodeList.item(i));
3777             }
3778             return nodes.stream();
3779         }
3780 
3781         Stream<Element> elements(XPathExpression xPathExpression) {
3782             return nodes(xPathExpression)
3783                     .filter(n -> n instanceof Element)
3784                     .map(n -> (Element) n);
3785         }
3786 
3787         Stream<XMLNode> xmlNodes(XPathExpression xPathExpression) {
3788             return elements(xPathExpression).map(e -> new XMLNode(e));
3789         }
3790     }
3791 
3792     public static class MavenStyleRepository {
3793         private final String repoBase = "https://repo1.maven.org/maven2/";
3794         private final String searchBase = "https://search.maven.org/solrsearch/";
3795         public RepoDir dir;
3796 
3797         JarFile jarFile(Id id) {
3798             return dir.jarFile(id.artifactAndVersion() + ".jar");
3799         }
3800 
3801         XMLFile pomFile(Id id) {
3802             return dir.xmlFile(id.artifactAndVersion() + ".pom");
3803         }
3804 
3805         public enum Scope {
3806             TEST,
3807             COMPILE,
3808             PROVIDED,
3809             RUNTIME,
3810             SYSTEM;
3811 
3812             static Scope of(String name) {
3813                 return switch (name.toLowerCase()) {
3814                     case "test" -> TEST;
3815                     case "compile" -> COMPILE;
3816                     case "provided" -> PROVIDED;
3817                     case "runtime" -> RUNTIME;
3818                     case "system" -> SYSTEM;
3819                     default -> COMPILE;
3820                 };
3821             }
3822         }
3823 
3824         public record GroupAndArtifactId(GroupId groupId, ArtifactId artifactId) {
3825 
3826             public static GroupAndArtifactId of(String groupAndArtifactId) {
3827                 int idx = groupAndArtifactId.indexOf('/');
3828                 return of(groupAndArtifactId.substring(0, idx), groupAndArtifactId.substring(idx + 1));
3829             }
3830 
3831             public static GroupAndArtifactId of(GroupId groupId, ArtifactId artifactId) {
3832                 return new GroupAndArtifactId(groupId, artifactId);
3833             }
3834 
3835             public static GroupAndArtifactId of(String groupId, String artifactId) {
3836                 return of(GroupId.of(groupId), ArtifactId.of(artifactId));
3837             }
3838 
3839             String location() {
3840                 return groupId().string().replace('.', '/') + "/" + artifactId().string();
3841             }
3842 
3843             @Override
3844             public String toString() {
3845                 return groupId() + "/" + artifactId();
3846             }
3847         }
3848 
3849         public sealed interface Id permits DependencyId, MetaDataId {
3850             MavenStyleRepository mavenStyleRepository();
3851 
3852             GroupAndArtifactId groupAndArtifactId();
3853 
3854             VersionId versionId();
3855 
3856             default String artifactAndVersion() {
3857                 return groupAndArtifactId().artifactId().string() + '-' + versionId();
3858             }
3859 
3860             default String location() {
3861                 return mavenStyleRepository().repoBase + groupAndArtifactId().location() + "/" + versionId();
3862             }
3863 
3864             default URL url(String suffix) {
3865                 try {
3866                     return new URI(location() + "/" + artifactAndVersion() + "." + suffix).toURL();
3867                 } catch (MalformedURLException | URISyntaxException e) {
3868                     throw new RuntimeException(e);
3869                 }
3870             }
3871         }
3872 
3873         public record DependencyId(
3874                 MavenStyleRepository mavenStyleRepository,
3875                 GroupAndArtifactId groupAndArtifactId,
3876                 VersionId versionId,
3877                 Scope scope,
3878                 boolean required)
3879                 implements Id {
3880             @Override
3881             public String toString() {
3882                 return groupAndArtifactId().toString()
3883                         + "/"
3884                         + versionId()
3885                         + ":"
3886                         + scope.toString()
3887                         + ":"
3888                         + (required ? "Required" : "Optiona");
3889             }
3890         }
3891 
3892         public record Pom(MetaDataId metaDataId, XMLNode xmlNode) {
3893             JarFile getJar() {
3894                 var jarFile = metaDataId.mavenStyleRepository().jarFile(metaDataId); // ;
3895                 metaDataId.mavenStyleRepository.queryAndCache(metaDataId.jarURL(), jarFile);
3896                 return jarFile;
3897             }
3898 
3899             String description() {
3900                 return xmlNode().xpathQueryString("/project/description/text()");
3901             }
3902 
3903             Stream<DependencyId> dependencies() {
3904                 return xmlNode()
3905                         .nodes(xmlNode.xpath("/project/dependencies/dependency"))
3906                         .map(node -> new XMLNode((Element) node))
3907                         .map(
3908                                 dependency ->
3909                                         new DependencyId(
3910                                                 metaDataId().mavenStyleRepository(),
3911                                                 GroupAndArtifactId.of(
3912                                                         GroupId.of(dependency.xpathQueryString("groupId/text()")),
3913                                                         ArtifactId.of(dependency.xpathQueryString("artifactId/text()"))),
3914                                                 VersionId.of(dependency.xpathQueryString("version/text()")),
3915                                                 Scope.of(dependency.xpathQueryString("scope/text()")),
3916                                                 !Boolean.parseBoolean(dependency.xpathQueryString("optional/text()"))));
3917             }
3918 
3919             Stream<DependencyId> requiredDependencies() {
3920                 return dependencies().filter(DependencyId::required);
3921             }
3922         }
3923 
3924         public Optional<Pom> pom(Id id) {
3925             return switch (id) {
3926                 case MetaDataId metaDataId -> {
3927                     if (metaDataId.versionId() == VersionId.UNSPECIFIED) {
3928                         // println("what to do when the version is unspecified");
3929                         yield Optional.empty();
3930                     }
3931                     try {
3932                         yield Optional.of(
3933                                 new Pom(
3934                                         metaDataId,
3935                                         queryAndCache(
3936                                                 metaDataId.pomURL(), metaDataId.mavenStyleRepository.pomFile(metaDataId))));
3937                     } catch (Throwable e) {
3938                         throw new RuntimeException(e);
3939                     }
3940                 }
3941                 case DependencyId dependencyId -> {
3942                     if (metaData(
3943                             id.groupAndArtifactId().groupId().string(),
3944                             id.groupAndArtifactId().artifactId().string())
3945                             instanceof Optional<MetaData> optionalMetaData
3946                             && optionalMetaData.isPresent()) {
3947                         if (optionalMetaData
3948                                 .get()
3949                                 .metaDataIds()
3950                                 .filter(metaDataId -> metaDataId.versionId().equals(id.versionId()))
3951                                 .findFirst()
3952                                 instanceof Optional<MetaDataId> metaId
3953                                 && metaId.isPresent()) {
3954                             yield pom(metaId.get());
3955                         } else {
3956                             yield Optional.empty();
3957                         }
3958                     } else {
3959                         yield Optional.empty();
3960                     }
3961                 }
3962 
3963             };
3964         }
3965 
3966         public Optional<Pom> pom(GroupAndArtifactId groupAndArtifactId) {
3967             var metaData = metaData(groupAndArtifactId).orElseThrow();
3968             var metaDataId = metaData.latestMetaDataId().orElseThrow();
3969             return pom(metaDataId);
3970         }
3971 
3972         record IdVersions(GroupAndArtifactId groupAndArtifactId, Set<Id> versions) {
3973             static IdVersions of(GroupAndArtifactId groupAndArtifactId) {
3974                 return new IdVersions(groupAndArtifactId, new HashSet<>());
3975             }
3976         }
3977 
3978         public static class Dag implements ClassPathEntryProvider {
3979             private final MavenStyleRepository repo;
3980             private final List<GroupAndArtifactId> rootGroupAndArtifactIds;
3981             Map<GroupAndArtifactId, IdVersions> nodes = new HashMap<>();
3982             Map<IdVersions, List<IdVersions>> edges = new HashMap<>();
3983 
3984             Dag add(Id from, Id to) {
3985                 var fromNode =
3986                         nodes.computeIfAbsent(
3987                                 from.groupAndArtifactId(), _ -> IdVersions.of(from.groupAndArtifactId()));
3988                 fromNode.versions().add(from);
3989                 var toNode =
3990                         nodes.computeIfAbsent(
3991                                 to.groupAndArtifactId(), _ -> IdVersions.of(to.groupAndArtifactId()));
3992                 toNode.versions().add(to);
3993                 edges.computeIfAbsent(fromNode, k -> new ArrayList<>()).add(toNode);
3994                 return this;
3995             }
3996 
3997             void removeUNSPECIFIED() {
3998                 nodes
3999                         .values()
4000                         .forEach(
4001                                 idversions -> {
4002                                     if (idversions.versions().size() > 1) {
4003                                         List<Id> versions = new ArrayList<>(idversions.versions());
4004                                         idversions.versions().clear();
4005                                         idversions
4006                                                 .versions()
4007                                                 .addAll(
4008                                                         versions.stream()
4009                                                                 .filter(v -> !v.versionId().equals(VersionId.UNSPECIFIED))
4010                                                                 .toList());
4011                                         println(idversions);
4012                                     }
4013                                     if (idversions.versions().size() > 1) {
4014                                         throw new IllegalStateException("more than one version");
4015                                     }
4016                                 });
4017             }
4018 
4019             Dag(MavenStyleRepository repo, List<GroupAndArtifactId> rootGroupAndArtifactIds) {
4020                 this.repo = repo;
4021                 this.rootGroupAndArtifactIds = rootGroupAndArtifactIds;
4022 
4023                 Set<Id> unresolved = new HashSet<>();
4024                 rootGroupAndArtifactIds.forEach(
4025                         rootGroupAndArtifactId -> {
4026                             var metaData = repo.metaData(rootGroupAndArtifactId).orElseThrow();
4027                             var metaDataId = metaData.latestMetaDataId().orElseThrow();
4028                             var optionalPom = repo.pom(rootGroupAndArtifactId);
4029 
4030                             if (optionalPom.isPresent() && optionalPom.get() instanceof Pom pom) {
4031                                 pom.requiredDependencies()
4032                                         .filter(dependencyId -> !dependencyId.scope.equals(Scope.TEST))
4033                                         .forEach(
4034                                                 dependencyId -> {
4035                                                     add(metaDataId, dependencyId);
4036                                                     unresolved.add(dependencyId);
4037                                                 });
4038                             }
4039                         });
4040 
4041                 while (!unresolved.isEmpty()) {
4042                     var resolveSet = new HashSet<>(unresolved);
4043                     unresolved.clear();
4044                     resolveSet.forEach(id -> {
4045                         if (repo.pom(id) instanceof Optional<Pom> p && p.isPresent()) {
4046                             p.get()
4047                                     .requiredDependencies()
4048                                     .filter(dependencyId -> !dependencyId.scope.equals(Scope.TEST))
4049                                     .forEach(
4050                                             dependencyId -> {
4051                                                 unresolved.add(dependencyId);
4052                                                 add(id, dependencyId);
4053                                             });
4054                         }
4055                     });
4056                 }
4057                 removeUNSPECIFIED();
4058             }
4059 
4060             @Override
4061             public List<ClassPathEntry> classPathEntries() {
4062                 return classPath().classPathEntries();
4063             }
4064 
4065             ClassPath classPath() {
4066 
4067                 ClassPath jars = ClassPath.of();
4068                 nodes
4069                         .keySet()
4070                         .forEach(
4071                                 id -> {
4072                                     Optional<Pom> optionalPom = repo.pom(id);
4073                                     if (optionalPom.isPresent() && optionalPom.get() instanceof Pom pom) {
4074                                         jars.add(pom.getJar());
4075                                     } else {
4076                                         throw new RuntimeException("No pom for " + id + " needed by " + id);
4077                                     }
4078                                 });
4079                 return jars;
4080             }
4081         }
4082 
4083         public ClassPathEntryProvider classPathEntries(String... rootGroupAndArtifactIds) {
4084             return classPathEntries(Stream.of(rootGroupAndArtifactIds).map(GroupAndArtifactId::of).toList());
4085         }
4086 
4087         public ClassPathEntryProvider classPathEntries(GroupAndArtifactId... rootGroupAndArtifactIds) {
4088             return classPathEntries(List.of(rootGroupAndArtifactIds));
4089         }
4090 
4091         public ClassPathEntryProvider classPathEntries(List<GroupAndArtifactId> rootGroupAndArtifactIds) {
4092             StringBuilder sb = new StringBuilder();
4093             rootGroupAndArtifactIds.forEach(groupAndArtifactId -> sb.append(sb.isEmpty() ? "" : "-").append(groupAndArtifactId.groupId + "-" + groupAndArtifactId.artifactId));
4094             System.out.println(sb);
4095             ClassPathEntryProvider classPathEntries = null;
4096             var pathFileName = sb + "-path.xml";
4097             var pathFile = dir.xmlFile(pathFileName);
4098             if (pathFile.exists()) {
4099                 System.out.println(pathFileName + " exists " + pathFile.path().toString());
4100                 XMLNode path = new XMLNode(pathFile.path());
4101                 ClassPath classPath = ClassPath.of();
4102                 path.nodes(path.xpath("/path/jar/text()")).forEach(e ->
4103                         classPath.add(dir.jarFile(e.getNodeValue()))
4104                 );
4105                 classPathEntries = classPath;
4106             } else {
4107                 var finalClassPathEntries = new Dag(this, rootGroupAndArtifactIds);
4108                 XMLNode.create("path", xml -> {
4109                     finalClassPathEntries.classPathEntries().forEach(cpe ->
4110                             xml.element("jar", jar -> jar.text(dir.path().relativize(cpe.path()).toString()))
4111                     );
4112                 }).write(pathFile);
4113                 System.out.println("created " + pathFile.path());
4114                 classPathEntries = finalClassPathEntries;
4115             }
4116             return classPathEntries;
4117         }
4118 
4119         public record VersionId(Integer maj, Integer min, Integer point, String classifier)
4120                 implements Comparable<VersionId> {
4121             static Integer integerOrNull(String s) {
4122                 return (s == null || s.isEmpty()) ? null : Integer.parseInt(s);
4123             }
4124 
4125             public static Pattern pattern = Pattern.compile("^(\\d+)(?:\\.(\\d+)(?:\\.(\\d+)(.*))?)?$");
4126             static VersionId UNSPECIFIED = new VersionId(null, null, null, null);
4127 
4128             static VersionId of(String version) {
4129                 Matcher matcher = pattern.matcher(version);
4130                 if (matcher.matches()) {
4131                     return new VersionId(
4132                             integerOrNull(matcher.group(1)),
4133                             integerOrNull(matcher.group(2)),
4134                             integerOrNull(matcher.group(3)),
4135                             matcher.group(4));
4136                 } else {
4137                     return UNSPECIFIED;
4138                 }
4139             }
4140 
4141             int cmp(Integer v1, Integer v2) {
4142                 if (v1 == null && v2 == null) {
4143                     return 0;
4144                 }
4145                 if (v1 == null) {
4146                     return -v2;
4147                 } else if (v2 == null) {
4148                     return v1;
4149                 } else {
4150                     return v1 - v2;
4151                 }
4152             }
4153 
4154             @Override
4155             public int compareTo(VersionId o) {
4156                 if (cmp(maj(), o.maj()) == 0) {
4157                     if (cmp(min(), o.min()) == 0) {
4158                         if (cmp(point(), o.point()) == 0) {
4159                             return classifier().compareTo(o.classifier());
4160                         } else {
4161                             return cmp(point(), o.point());
4162                         }
4163                     } else {
4164                         return cmp(min(), o.min());
4165                     }
4166                 } else {
4167                     return cmp(maj(), o.maj());
4168                 }
4169             }
4170 
4171             @Override
4172             public String toString() {
4173                 StringBuilder sb = new StringBuilder();
4174                 if (maj() != null) {
4175                     sb.append(maj());
4176                     if (min() != null) {
4177                         sb.append(".").append(min());
4178                         if (point() != null) {
4179                             sb.append(".").append(point());
4180                             if (classifier() != null) {
4181                                 sb.append(classifier());
4182                             }
4183                         }
4184                     }
4185                 } else {
4186                     sb.append("UNSPECIFIED");
4187                 }
4188                 return sb.toString();
4189             }
4190         }
4191 
4192         public record GroupId(String string) {
4193             public static GroupId of(String s) {
4194                 return new GroupId(s);
4195             }
4196 
4197             @Override
4198             public String toString() {
4199                 return string;
4200             }
4201         }
4202 
4203         public record ArtifactId(String string) {
4204             static ArtifactId of(String string) {
4205                 return new ArtifactId(string);
4206             }
4207 
4208             @Override
4209             public String toString() {
4210                 return string;
4211             }
4212         }
4213 
4214         public record MetaDataId(
4215                 MavenStyleRepository mavenStyleRepository,
4216                 GroupAndArtifactId groupAndArtifactId,
4217                 VersionId versionId,
4218                 Set<String> downloadables,
4219                 Set<String> tags)
4220                 implements Id {
4221 
4222             public URL pomURL() {
4223                 return url("pom");
4224             }
4225 
4226             public URL jarURL() {
4227                 return url("jar");
4228             }
4229 
4230             public XMLNode getPom() {
4231                 if (downloadables.contains(".pom")) {
4232                     return mavenStyleRepository.queryAndCache(
4233                             url("pom"), mavenStyleRepository.dir.xmlFile(artifactAndVersion() + ".pom"));
4234                 } else {
4235                     throw new IllegalStateException("no pom");
4236                 }
4237             }
4238 
4239             @Override
4240             public String toString() {
4241                 return groupAndArtifactId().toString() + "." + versionId();
4242             }
4243         }
4244 
4245         public MavenStyleRepository(RepoDir dir) {
4246             this.dir = dir.create();
4247         }
4248 
4249         JarFile queryAndCache(URL query, JarFile jarFile) {
4250             try {
4251                 if (!jarFile.exists()) {
4252                     print("Querying and caching " + jarFile.fileName());
4253                     println(" downloading " + query);
4254                     curl(query, jarFile.path());
4255                 } else {
4256                     // println("Using cached " + jarFile.fileName());
4257 
4258                 }
4259             } catch (Throwable e) {
4260                 throw new RuntimeException(e);
4261             }
4262             return jarFile;
4263         }
4264 
4265         XMLNode queryAndCache(URL query, XMLFile xmlFile) {
4266             XMLNode xmlNode = null;
4267             try {
4268                 if (!xmlFile.exists()) {
4269                     print("Querying and caching " + xmlFile.fileName());
4270                     println(" downloading " + query);
4271                     xmlNode = new XMLNode(query);
4272                     xmlNode.write(xmlFile.path().toFile());
4273                 } else {
4274                     // println("Using cached " + xmlFile.fileName());
4275                     xmlNode = new XMLNode(xmlFile.path());
4276                 }
4277             } catch (Throwable e) {
4278                 throw new RuntimeException(e);
4279             }
4280             return xmlNode;
4281         }
4282 
4283         public record MetaData(
4284                 MavenStyleRepository mavenStyleRepository,
4285                 GroupAndArtifactId groupAndArtifactId,
4286                 XMLNode xmlNode) {
4287 
4288             public Stream<MetaDataId> metaDataIds() {
4289                 return xmlNode
4290                         .xmlNodes(xmlNode.xpath("/response/result/doc"))
4291                         .map(
4292                                 xmln ->
4293                                         new MetaDataId(
4294                                                 this.mavenStyleRepository,
4295                                                 GroupAndArtifactId.of(
4296                                                         GroupId.of(xmln.xpathQueryString("str[@name='g']/text()")),
4297                                                         ArtifactId.of(xmln.xpathQueryString("str[@name='a']/text()"))),
4298                                                 VersionId.of(xmln.xpathQueryString("str[@name='v']/text()")),
4299                                                 new HashSet<>(
4300                                                         xmln.nodes(xmln.xpath("arr[@name='ec']/str/text()"))
4301                                                                 .map(Node::getNodeValue)
4302                                                                 .toList()),
4303                                                 new HashSet<>(
4304                                                         xmln.nodes(xmln.xpath("arr[@name='tags']/str/text()"))
4305                                                                 .map(Node::getNodeValue)
4306                                                                 .toList())));
4307             }
4308 
4309             public Stream<MetaDataId> sortedMetaDataIds() {
4310                 return metaDataIds().sorted(Comparator.comparing(MetaDataId::versionId));
4311             }
4312 
4313             public Optional<MetaDataId> latestMetaDataId() {
4314                 return metaDataIds().max(Comparator.comparing(MetaDataId::versionId));
4315             }
4316 
4317             public Optional<MetaDataId> getMetaDataId(VersionId versionId) {
4318                 return metaDataIds().filter(id -> versionId.compareTo(id.versionId()) == 0).findFirst();
4319             }
4320         }
4321 
4322         public Optional<MetaData> metaData(String groupId, String artifactId) {
4323             return metaData(GroupAndArtifactId.of(groupId, artifactId));
4324         }
4325 
4326         public Optional<MetaData> metaData(GroupAndArtifactId groupAndArtifactId) {
4327             try {
4328                 var query = "g:" + groupAndArtifactId.groupId() + " AND a:" + groupAndArtifactId.artifactId();
4329                 URL rowQueryUrl =
4330                         new URI(
4331                                 searchBase
4332                                         + "select?q="
4333                                         + URLEncoder.encode(query, StandardCharsets.UTF_8)
4334                                         + "&core=gav&wt=xml&rows=0")
4335                                 .toURL();
4336                 var rowQueryResponse = new XMLNode(rowQueryUrl);
4337                 var numFound = rowQueryResponse.xpathQueryString("/response/result/@numFound");
4338 
4339                 URL url =
4340                         new URI(
4341                                 searchBase
4342                                         + "select?q="
4343                                         + URLEncoder.encode(query, StandardCharsets.UTF_8)
4344                                         + "&core=gav&wt=xml&rows="
4345                                         + numFound)
4346                                 .toURL();
4347                 try {
4348                     // println(url);
4349                     var xmlNode =
4350                             queryAndCache(url, dir.xmlFile(groupAndArtifactId.artifactId() + ".meta.xml"));
4351                     if (numFound.isEmpty() || numFound.equals("0")) {
4352                         return Optional.empty();
4353                     } else {
4354                         return Optional.of(new MetaData(this, groupAndArtifactId, xmlNode));
4355                     }
4356                 } catch (Throwable e) {
4357                     throw new RuntimeException(e);
4358                 }
4359             } catch (Throwable e) {
4360                 throw new RuntimeException(e);
4361             }
4362         }
4363     }
4364 
4365     public static class IntelliJ {
4366         public static class IntellijArtifact {
4367             DirEntry projectDir;
4368             XMLNode root;
4369 
4370             Stream<XMLNode> query(String xpath) {
4371                 return root.nodes(root.xpath(xpath)).map(e -> new XMLNode((Element) e));
4372             }
4373 
4374             IntellijArtifact(DirEntry projectDir, XMLNode root) {
4375                 this.projectDir = projectDir;
4376                 this.root = root;
4377             }
4378         }
4379 
4380         public static class Workspace extends IntellijArtifact {
4381 
4382             record Application(XMLNode xmlNode) {
4383             }
4384 
4385             List<Application> applications;
4386 
4387             Workspace(DirEntry projectDir, XMLNode root) {
4388                 super(projectDir, root);
4389                 this.applications =
4390                         query("/project/component[@name='RunManager']/configuration")
4391                                 .map(Application::new)
4392                                 .toList();
4393             }
4394         }
4395 
4396         public static class Compiler extends IntellijArtifact {
4397             public record JavacSettings(XMLNode xmlNode) {
4398                 public String getAdditionalOptions() {
4399                     return xmlNode.xpathQueryString("option[@name='ADDITIONAL_OPTIONS_STRING']/@value");
4400                 }
4401             }
4402 
4403             public JavacSettings javacSettings;
4404 
4405             Compiler(DirEntry projectDir, XMLNode root) {
4406                 super(projectDir, root);
4407                 this.javacSettings =
4408                         new JavacSettings(query("/project/component[@name='JavacSettings']").findFirst().get());
4409             }
4410         }
4411 
4412         public static class ImlGraph extends IntellijArtifact {
4413             public record Module(Path imlPath, XMLNode xmlNode) {
4414                 @Override
4415                 public String toString() {
4416                     return name();
4417                 }
4418 
4419                 public String name() {
4420                     return imlPath.getFileName().toString();
4421                 }
4422 
4423                 public SourcePath getSourcePath() {
4424                     return null;
4425                 }
4426 
4427                 Stream<XMLNode> query(String xpath) {
4428                     return xmlNode.nodes(xmlNode.xpath(xpath)).map(e -> new XMLNode((Element) e));
4429                 }
4430             }
4431 
4432             Stream<XMLNode> query(String xpath) {
4433                 return root.nodes(root.xpath(xpath)).map(e -> new XMLNode((Element) e));
4434             }
4435 
4436             Set<Module> modules = new HashSet<>();
4437             public Map<Module, List<Module>> fromToDependencies = new HashMap<>();
4438             Map<Module, List<Module>> toFromDependencies = new HashMap<>();
4439 
4440             ImlGraph(DirEntry projectDir, XMLNode root) {
4441                 super(projectDir, root);
4442                 Map<String, Module> nameToModule = new HashMap<>();
4443                 query("/project/component[@name='ProjectModuleManager']/modules/module")
4444                         .map(
4445                                 xmlNode ->
4446                                         Path.of(
4447                                                 xmlNode
4448                                                         .attrMap
4449                                                         .get("filepath")
4450                                                         .replace("$PROJECT_DIR$", projectDir.path().toString())))
4451                         .map(path -> new Module(path, new XMLNode(path)))
4452                         .forEach(
4453                                 module -> {
4454                                     modules.add(module);
4455                                     nameToModule.put(module.name(), module);
4456                                 });
4457                 modules.forEach(
4458                         module ->
4459                                 module
4460                                         .xmlNode
4461                                         .nodes(root.xpath("/module/component/orderEntry[@type='module']"))
4462                                         .map(e -> new XMLNode((Element) e))
4463                                         .forEach(
4464                                                 e -> {
4465                                                     var dep = nameToModule.get(e.attrMap.get("module-name") + ".iml");
4466                                                     fromToDependencies.computeIfAbsent(module, _ -> new ArrayList<>()).add(dep);
4467                                                     toFromDependencies.computeIfAbsent(dep, _ -> new ArrayList<>()).add(module);
4468                                                 }));
4469             }
4470         }
4471 
4472         public static class Project {
4473             public DirEntry intellijDir;
4474             public ImlGraph imlGraph;
4475             public Workspace workSpace;
4476             public Compiler compiler;
4477 
4478             public Project(DirEntry intellijDir) {
4479                 this.intellijDir = intellijDir;
4480                 var ideaDir = intellijDir.existingDir(".idea");
4481                 imlGraph = new ImlGraph(intellijDir, new XMLNode(ideaDir.xmlFile("modules.xml").path()));
4482                 workSpace = new Workspace(intellijDir, new XMLNode(ideaDir.xmlFile("workspace.xml").path()));
4483                 compiler = new Compiler(intellijDir, new XMLNode(ideaDir.xmlFile("compiler.xml").path()));
4484             }
4485         }
4486     }
4487 }