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}