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