001package com.github.sormuras.bach.command;
002
003import com.github.sormuras.bach.Command;
004import java.nio.file.Files;
005import java.nio.file.Path;
006import java.util.ArrayList;
007import java.util.List;
008import java.util.Optional;
009
010/**
011 * The jar command creates an archive for classes and resources, and manipulates or restores
012 * individual classes or resources from an archive.
013 *
014 * @param mode The main operation mode like {@code --create}, {@code --list}, or {@code --update}.
015 * @param file Specifies the archive file name.
016 * @param main Specifies the entry point for standalone applications bundled into a JAR file.
017 * @param verbose Sends or prints verbose output to standard output.
018 * @param additionals Aggregates additional command-line arguments.
019 * @param files Specifies the classes and resources to operate on.
020 * @see <a href="https://docs.oracle.com/en/java/javase/17/docs/specs/man/jar.html">jar</a>
021 */
022public record JarCommand(
023    ModeOption mode,
024    FileOption file,
025    MainClassOption main,
026    VerboseOption verbose,
027    AdditionalArgumentsOption additionals,
028    FilesOption files)
029    implements Command<JarCommand> {
030
031  public JarCommand() {
032    this(
033        ModeOption.empty(),
034        FileOption.empty(),
035        MainClassOption.empty(),
036        VerboseOption.empty(),
037        AdditionalArgumentsOption.empty(),
038        FilesOption.empty());
039  }
040
041  @Override
042  public String name() {
043    return "jar";
044  }
045
046  @Override
047  public JarCommand option(Option option) {
048    return new JarCommand(
049        option instanceof ModeOption mode ? mode : mode,
050        option instanceof FileOption file ? file : file,
051        option instanceof MainClassOption main ? main : main,
052        option instanceof VerboseOption verbose ? verbose : verbose,
053        option instanceof AdditionalArgumentsOption additionals ? additionals : additionals,
054        option instanceof FilesOption files ? files : files);
055  }
056
057  public JarCommand mode(String mode) {
058    return option(new ModeOption(Optional.ofNullable(mode)));
059  }
060
061  public JarCommand file(Path file) {
062    return option(new FileOption(Optional.ofNullable(file)));
063  }
064
065  public JarCommand main(String main) {
066    return option(new MainClassOption(Optional.ofNullable(main)));
067  }
068
069  public JarCommand verbose(Boolean verbose) {
070    return option(new VerboseOption(Optional.ofNullable(verbose)));
071  }
072
073  @Override
074  public JarCommand additionals(AdditionalArgumentsOption additionals) {
075    return option(additionals);
076  }
077
078  public JarCommand filesAdd(Path path) {
079    return filesAdd(0, path);
080  }
081
082  public JarCommand filesAdd(int version, Path path) {
083    return option(files.add(version, path));
084  }
085
086  @Override
087  public List<String> toArguments() {
088    var jar = Command.of(name());
089    if (mode.isPresent()) jar = jar.add(mode.get());
090    if (file.isPresent()) jar = jar.add("--file", file.get());
091    if (main.isPresent()) jar = jar.add("--main-class", main.get());
092    if (verbose.isTrue()) jar = jar.add("--verbose");
093    //
094    jar = jar.addAll(additionals.values());
095    //
096    if (files.isPresent()) {
097      for (int version = 0; version <= Runtime.version().feature(); version++) {
098        var targeting = files.targeting(version);
099        if (targeting.isEmpty()) continue;
100        if (version != 0) jar = jar.add("--release", version);
101        for (var targeted : targeting) {
102          for (var path : targeted.paths()) {
103            jar = Files.isDirectory(path) ? jar.add("-C", path, ".") : jar.add(path);
104          }
105        }
106      }
107    }
108    return jar.toArguments();
109  }
110
111  /** Main operation mode option. */
112  public record ModeOption(Optional<String> value) implements Option.Value<String> {
113    public static ModeOption empty() {
114      return new ModeOption(Optional.empty());
115    }
116  }
117
118  /** Archive file name option. */
119  public record FileOption(Optional<Path> value) implements Option.Value<Path> {
120    public static FileOption empty() {
121      return new FileOption(Optional.empty());
122    }
123  }
124
125  /** Entry point for standalone applications option. */
126  public record MainClassOption(Optional<String> value) implements Option.Value<String> {
127    public static MainClassOption empty() {
128      return new MainClassOption(Optional.empty());
129    }
130  }
131
132  /** Directories and regular files option. */
133  public record FilesOption(List<TargetedPaths> values) implements Option.Values<TargetedPaths> {
134    public static FilesOption empty() {
135      return new FilesOption(List.of());
136    }
137
138    public FilesOption add(int version, Path path) {
139      var values = new ArrayList<>(this.values);
140      values.add(new TargetedPaths(version, List.of(path)));
141      return new FilesOption(values);
142    }
143
144    public List<TargetedPaths> targeting(int version) {
145      return values.stream().filter(targeted -> targeted.version() == version).toList();
146    }
147  }
148}