| 1 | package com.github.dakusui.symfonion.cli; | |
| 2 | ||
| 3 | import com.github.dakusui.logias.lisp.Context; | |
| 4 | import com.github.dakusui.symfonion.cli.subcommands.PresetSubcommand; | |
| 5 | import com.github.dakusui.symfonion.core.Symfonion; | |
| 6 | import com.github.dakusui.symfonion.exceptions.CliException; | |
| 7 | import com.github.dakusui.symfonion.exceptions.SymfonionException; | |
| 8 | import org.apache.commons.cli.*; | |
| 9 | ||
| 10 | import javax.swing.*; | |
| 11 | import java.awt.*; | |
| 12 | import java.awt.event.WindowAdapter; | |
| 13 | import java.awt.event.WindowEvent; | |
| 14 | import java.io.File; | |
| 15 | import java.io.IOException; | |
| 16 | import java.io.OutputStream; | |
| 17 | import java.io.PrintStream; | |
| 18 | import java.util.HashMap; | |
| 19 | import java.util.List; | |
| 20 | import java.util.Map; | |
| 21 | import java.util.Properties; | |
| 22 | import java.util.regex.Pattern; | |
| 23 | import java.util.regex.PatternSyntaxException; | |
| 24 | ||
| 25 | import static com.github.dakusui.symfonion.cli.CliUtils.composeErrMsg; | |
| 26 | import static java.lang.String.format; | |
| 27 | ||
| 28 | public record Cli(Subcommand subcommand, File source, File sink, MidiRouteRequest routeRequest, | |
| 29 | /* | |
| 30 | * Returns a map that defines MIDI-in port names. | |
| 31 | * A key in the returned map is a port name used in a symfonion song file. | |
| 32 | * The value associated with it is a regular expression that should specify a MIDI device. | |
| 33 | * The regular expression should be defined so that it matches one and only one MIDI-in device available in the system. | |
| 34 | */ | |
| 35 | Map<String, Pattern> midiInRegexPatterns, | |
| 36 | /* | |
| 37 | * Returns a map that defines MIDI-out port names. | |
| 38 | * A key in the returned map is a port name used in a symfonion song file. | |
| 39 | * The value associated with it is a regular expression that should specify a MIDI device. | |
| 40 | * The regular expression should be defined so that it matches one and only one MIDI-out device available in the system. | |
| 41 | */ | |
| 42 | Map<String, Pattern> midiOutRegexPatterns, | |
| 43 | Options options, | |
| 44 | Symfonion symfonion) { | |
| 45 | ||
| 46 | /** | |
| 47 | * Returns an {@code Options} object which represents the specification of this CLI command. | |
| 48 | * | |
| 49 | * @return an {@code Options} object for this {@code CLI} class. | |
| 50 | */ | |
| 51 | static Options buildOptions() { | |
| 52 | // create Options object | |
| 53 | Options options = new Options(); | |
| 54 | ||
| 55 | // // | |
| 56 | // Behavior options | |
| 57 | options.addOption("V", "version", false, "print the version information."); | |
| 58 | options.addOption("h", "help", false, "print the command line usage."); | |
| 59 | options.addOption("l", "list", false, "list the available midi devices."); | |
| 60 | options.addOption("p", "play", true, "play the specified file."); | |
| 61 | options.addOption("c", "compile", true, | |
| 62 | "compile the specified file to a standard midi file."); | |
| 63 | { | |
| 64 | Option option = OptionBuilder.create("r"); | |
| 65 |
1
1. buildOptions : removed call to org/apache/commons/cli/Option::setLongOpt → SURVIVED |
option.setLongOpt("route"); |
| 66 |
1
1. buildOptions : removed call to org/apache/commons/cli/Option::setValueSeparator → KILLED |
option.setValueSeparator('='); |
| 67 |
1
1. buildOptions : removed call to org/apache/commons/cli/Option::setArgs → KILLED |
option.setArgs(2); |
| 68 |
1
1. buildOptions : removed call to org/apache/commons/cli/Option::setDescription → SURVIVED |
option.setDescription("run a midi patch bay."); |
| 69 | options.addOption(option); | |
| 70 | } | |
| 71 | ||
| 72 | // // | |
| 73 | // I/O options | |
| 74 | { | |
| 75 | Option option = OptionBuilder.create("O"); | |
| 76 |
1
1. buildOptions : removed call to org/apache/commons/cli/Option::setValueSeparator → KILLED |
option.setValueSeparator('='); |
| 77 |
1
1. buildOptions : removed call to org/apache/commons/cli/Option::setArgs → KILLED |
option.setArgs(2); |
| 78 |
1
1. buildOptions : removed call to org/apache/commons/cli/Option::setDescription → SURVIVED |
option.setDescription("specify midi out port."); |
| 79 | options.addOption(option); | |
| 80 | } | |
| 81 | { | |
| 82 | Option option = OptionBuilder.create("I"); | |
| 83 |
1
1. buildOptions : removed call to org/apache/commons/cli/Option::setValueSeparator → KILLED |
option.setValueSeparator('='); |
| 84 |
1
1. buildOptions : removed call to org/apache/commons/cli/Option::setArgs → KILLED |
option.setArgs(2); |
| 85 |
1
1. buildOptions : removed call to org/apache/commons/cli/Option::setDescription → SURVIVED |
option.setDescription("specify midi in port."); |
| 86 | options.addOption(option); | |
| 87 | } | |
| 88 | { | |
| 89 | Option option = OptionBuilder.create("o"); | |
| 90 |
1
1. buildOptions : removed call to org/apache/commons/cli/Option::setArgs → SURVIVED |
option.setArgs(1); |
| 91 |
1
1. buildOptions : removed call to org/apache/commons/cli/Option::setDescription → SURVIVED |
option.setDescription("specify a file to which a compiled standard midi file is output."); |
| 92 | options.addOption(option); | |
| 93 | } | |
| 94 |
1
1. buildOptions : replaced return value with null for com/github/dakusui/symfonion/cli/Cli::buildOptions → KILLED |
return options; |
| 95 | } | |
| 96 | ||
| 97 | static Symfonion createSymfonion() { | |
| 98 |
1
1. createSymfonion : replaced return value with null for com/github/dakusui/symfonion/cli/Cli::createSymfonion → KILLED |
return new Symfonion(Context.ROOT.createChild()); |
| 99 | } | |
| 100 | ||
| 101 | static CommandLine parseArgs(Options options, String[] args) throws ParseException { | |
| 102 | CommandLineParser parser = new GnuParser(); | |
| 103 | ||
| 104 |
1
1. parseArgs : replaced return value with null for com/github/dakusui/symfonion/cli/Cli::parseArgs → KILLED |
return parser.parse(options, args); |
| 105 | } | |
| 106 | ||
| 107 | static Map<String, Pattern> parseSpecifiedOptionsInCommandLineAsPortNamePatterns(CommandLine cmd, String optionName) throws CliException { | |
| 108 | Properties props = cmd.getOptionProperties(optionName); | |
| 109 | Map<String, Pattern> ret = new HashMap<>(); | |
| 110 | for (Object key : props.keySet()) { | |
| 111 | String portName = key.toString(); | |
| 112 | String p = props.getProperty(portName); | |
| 113 | try { | |
| 114 | Pattern portpattern = Pattern.compile(p); | |
| 115 | ret.put(portName, portpattern); | |
| 116 | } catch (PatternSyntaxException e) { | |
| 117 | throw new CliException(composeErrMsg( | |
| 118 | format("Regular expression '%s' for '%s' isn't valid.", portName, p), | |
| 119 | optionName, | |
| 120 | null), e); | |
| 121 | } | |
| 122 | } | |
| 123 |
1
1. parseSpecifiedOptionsInCommandLineAsPortNamePatterns : replaced return value with Collections.emptyMap for com/github/dakusui/symfonion/cli/Cli::parseSpecifiedOptionsInCommandLineAsPortNamePatterns → KILLED |
return ret; |
| 124 | } | |
| 125 | ||
| 126 | public static int invoke(PrintStream stdout, PrintStream stderr, String... args) { | |
| 127 | int ret; | |
| 128 | try { | |
| 129 | Cli cli = new Builder(args).build(); | |
| 130 |
1
1. invoke : removed call to com/github/dakusui/symfonion/cli/Subcommand::invoke → KILLED |
cli.subcommand().invoke(cli, stdout, System.in); |
| 131 | ret = 0; | |
| 132 | } catch (ParseException e) { | |
| 133 |
1
1. invoke : removed call to com/github/dakusui/symfonion/cli/Cli::printError → NO_COVERAGE |
printError(stderr, e); |
| 134 | ret = 1; | |
| 135 | } catch (CliException e) { | |
| 136 |
1
1. invoke : removed call to com/github/dakusui/symfonion/cli/Cli::printError → NO_COVERAGE |
printError(stderr, e); |
| 137 | ret = 2; | |
| 138 | } catch (SymfonionException e) { | |
| 139 |
1
1. invoke : removed call to com/github/dakusui/symfonion/cli/Cli::printError → KILLED |
printError(stderr, e); |
| 140 | ret = 3; | |
| 141 | } catch (IOException e) { | |
| 142 |
1
1. invoke : removed call to java/io/IOException::printStackTrace → NO_COVERAGE |
e.printStackTrace(stderr); |
| 143 | ret = 4; | |
| 144 | } catch (Exception e) { | |
| 145 |
1
1. invoke : removed call to java/lang/Exception::printStackTrace → KILLED |
e.printStackTrace(stderr); |
| 146 | ret = 5; | |
| 147 | } | |
| 148 |
1
1. invoke : replaced int return with 0 for com/github/dakusui/symfonion/cli/Cli::invoke → KILLED |
return ret; |
| 149 | } | |
| 150 | ||
| 151 | private static void printError(PrintStream ps, Throwable t) { | |
| 152 | ps.printf("symfonion: %s%n", t.getMessage()); | |
| 153 | } | |
| 154 | ||
| 155 | public static void main(String... args) { | |
| 156 |
2
1. main : negated conditional → NO_COVERAGE 2. main : negated conditional → NO_COVERAGE |
if (args.length == 0 && !GraphicsEnvironment.isHeadless()) { |
| 157 |
1
1. main : removed call to com/github/dakusui/symfonion/cli/Cli::fallbackToSimpleGUI → NO_COVERAGE |
fallbackToSimpleGUI(); |
| 158 | } else { | |
| 159 | int exitCode = invoke(System.out, System.err, args); | |
| 160 |
1
1. main : removed call to java/lang/System::exit → NO_COVERAGE |
System.exit(exitCode); |
| 161 | } | |
| 162 | } | |
| 163 | ||
| 164 | static void fallbackToSimpleGUI() { | |
| 165 | String selectedFile = filenameFromFileChooser(); | |
| 166 |
1
1. fallbackToSimpleGUI : negated conditional → NO_COVERAGE |
if (selectedFile != null) { |
| 167 | String[] args = new String[]{selectedFile}; | |
| 168 | final JTextArea textArea = new JTextArea(); | |
| 169 | JFrame frame = new JFrame("symfonion output"); | |
| 170 |
1
1. fallbackToSimpleGUI : removed call to javax/swing/JFrame::addWindowListener → NO_COVERAGE |
frame.addWindowListener(new WindowAdapter() { |
| 171 | public void windowClosing(WindowEvent e) { | |
| 172 |
1
1. windowClosing : removed call to java/lang/System::exit → NO_COVERAGE |
System.exit(0); |
| 173 | } | |
| 174 | }); | |
| 175 | frame.add(textArea); | |
| 176 |
1
1. fallbackToSimpleGUI : removed call to javax/swing/JFrame::pack → NO_COVERAGE |
frame.pack(); |
| 177 |
1
1. fallbackToSimpleGUI : removed call to javax/swing/JFrame::setSize → NO_COVERAGE |
frame.setSize(800, 600); |
| 178 |
1
1. fallbackToSimpleGUI : removed call to javax/swing/JFrame::setVisible → NO_COVERAGE |
frame.setVisible(true); |
| 179 | PrintStream ps = new PrintStream(new OutputStream() { | |
| 180 | private final StringBuilder sb = new StringBuilder(); | |
| 181 | ||
| 182 | @Override | |
| 183 | public void flush() { | |
| 184 | } | |
| 185 | ||
| 186 | @Override | |
| 187 | public void close() { | |
| 188 | } | |
| 189 | ||
| 190 | @Override | |
| 191 | public void write(int b) { | |
| 192 |
1
1. write : negated conditional → NO_COVERAGE |
if (b == '\r') |
| 193 | return; | |
| 194 | ||
| 195 |
1
1. write : negated conditional → NO_COVERAGE |
if (b == '\n') { |
| 196 | final String text = sb + "\n"; | |
| 197 |
2
1. write : removed call to javax/swing/SwingUtilities::invokeLater → NO_COVERAGE 2. lambda$write$0 : removed call to javax/swing/JTextArea::append → NO_COVERAGE |
SwingUtilities.invokeLater(() -> textArea.append(text)); |
| 198 |
1
1. write : removed call to java/lang/StringBuilder::setLength → NO_COVERAGE |
sb.setLength(0); |
| 199 | return; | |
| 200 | } | |
| 201 | ||
| 202 | sb.append((char) b); | |
| 203 | } | |
| 204 | ||
| 205 | }); | |
| 206 |
1
1. fallbackToSimpleGUI : removed call to java/lang/System::setOut → NO_COVERAGE |
System.setOut(ps); |
| 207 |
1
1. fallbackToSimpleGUI : removed call to java/lang/System::setErr → NO_COVERAGE |
System.setErr(ps); |
| 208 | invoke(System.out, System.err, args); | |
| 209 | } | |
| 210 | } | |
| 211 | ||
| 212 | static String filenameFromFileChooser() { | |
| 213 | JFileChooser chooser = new JFileChooser(); | |
| 214 |
1
1. filenameFromFileChooser : removed call to javax/swing/JFileChooser::setCurrentDirectory → NO_COVERAGE |
chooser.setCurrentDirectory(new File(System.getProperty("user.dir"))); |
| 215 | int result = chooser.showOpenDialog(new JFrame()); | |
| 216 |
1
1. filenameFromFileChooser : negated conditional → NO_COVERAGE |
if (result == JFileChooser.APPROVE_OPTION) { |
| 217 |
1
1. filenameFromFileChooser : replaced return value with "" for com/github/dakusui/symfonion/cli/Cli::filenameFromFileChooser → NO_COVERAGE |
return chooser.getSelectedFile().getAbsolutePath(); |
| 218 | } | |
| 219 |
1
1. filenameFromFileChooser : replaced return value with "" for com/github/dakusui/symfonion/cli/Cli::filenameFromFileChooser → NO_COVERAGE |
return null; |
| 220 | } | |
| 221 | ||
| 222 | public static class Builder { | |
| 223 | private final String[] args; | |
| 224 | private File source; | |
| 225 | private File sink = new File("a.midi"); | |
| 226 | private MidiRouteRequest routeRequest = null; | |
| 227 | private Map<String, Pattern> midiInRegexPatterns = new HashMap<>(); | |
| 228 | private Map<String, Pattern> midiOutRegexPatterns = new HashMap<>(); | |
| 229 | ||
| 230 | public Builder(String... args) { | |
| 231 | this.args = args; | |
| 232 | } | |
| 233 | ||
| 234 | public Cli build() throws ParseException { | |
| 235 | Options options1 = buildOptions(); | |
| 236 | CommandLine cmd = parseArgs(options1, args); | |
| 237 |
1
1. build : negated conditional → KILLED |
if (cmd.hasOption('O')) { |
| 238 | this.midiOutRegexPatterns = parseSpecifiedOptionsInCommandLineAsPortNamePatterns(cmd, "O"); | |
| 239 | } | |
| 240 |
1
1. build : negated conditional → KILLED |
if (cmd.hasOption('I')) { |
| 241 | this.midiInRegexPatterns = parseSpecifiedOptionsInCommandLineAsPortNamePatterns(cmd, "I"); | |
| 242 | } | |
| 243 |
1
1. build : negated conditional → KILLED |
if (cmd.hasOption('o')) { |
| 244 | String sinkFilename = CliUtils.getSingleOptionValueFromCommandLine(cmd, "o"); | |
| 245 |
1
1. build : negated conditional → NO_COVERAGE |
if (sinkFilename == null) { |
| 246 | throw new CliException(composeErrMsg("Output filename is required by this option.", "o")); | |
| 247 | } | |
| 248 | this.sink = new File(sinkFilename); | |
| 249 | } | |
| 250 | Subcommand subcommand; | |
| 251 |
2
1. build : negated conditional → KILLED 2. build : negated conditional → KILLED |
if (cmd.hasOption("V") || cmd.hasOption("version")) { |
| 252 | subcommand = PresetSubcommand.VERSION; | |
| 253 |
2
1. build : negated conditional → KILLED 2. build : negated conditional → KILLED |
} else if (cmd.hasOption("h") || cmd.hasOption("help")) { |
| 254 | subcommand = PresetSubcommand.HELP; | |
| 255 |
2
1. build : negated conditional → KILLED 2. build : negated conditional → KILLED |
} else if (cmd.hasOption("l") || cmd.hasOption("list")) { |
| 256 | subcommand = PresetSubcommand.LIST; | |
| 257 |
2
1. build : negated conditional → KILLED 2. build : negated conditional → KILLED |
} else if (cmd.hasOption("p") || cmd.hasOption("play")) { |
| 258 | subcommand = PresetSubcommand.PLAY; | |
| 259 | String sourceFilename = CliUtils.getSingleOptionValueFromCommandLine(cmd, "p"); | |
| 260 |
1
1. build : negated conditional → NO_COVERAGE |
if (sourceFilename == null) { |
| 261 | throw new CliException(composeErrMsg("Input filename is required by this option.", "p")); | |
| 262 | } | |
| 263 | this.source = new File(sourceFilename); | |
| 264 |
2
1. build : negated conditional → KILLED 2. build : negated conditional → KILLED |
} else if (cmd.hasOption("c") || cmd.hasOption("compile")) { |
| 265 | subcommand = PresetSubcommand.COMPILE; | |
| 266 | String sourceFilename = CliUtils.getSingleOptionValueFromCommandLine(cmd, "c"); | |
| 267 |
1
1. build : negated conditional → KILLED |
if (sourceFilename == null) { |
| 268 | throw new CliException(composeErrMsg("Input filename is required by this option.", "c")); | |
| 269 | } | |
| 270 | this.source = new File(sourceFilename); | |
| 271 |
2
1. build : negated conditional → KILLED 2. build : negated conditional → KILLED |
} else if (cmd.hasOption("r") || cmd.hasOption("route")) { |
| 272 | subcommand = PresetSubcommand.ROUTE; | |
| 273 | Properties props = cmd.getOptionProperties("r"); | |
| 274 |
1
1. build : negated conditional → KILLED |
if (props.size() != 1) { |
| 275 | throw new CliException(composeErrMsg("Route information is not given or specified multiple times.", "r", "route")); | |
| 276 | } | |
| 277 | ||
| 278 | this.routeRequest = new MidiRouteRequest(cmd.getOptionValues('r')[0], cmd.getOptionValues('r')[1]); | |
| 279 | } else { | |
| 280 | @SuppressWarnings("unchecked") | |
| 281 | List<String> leftovers = cmd.getArgList(); | |
| 282 |
1
1. build : negated conditional → KILLED |
if (leftovers.isEmpty()) { |
| 283 | subcommand = PresetSubcommand.HELP; | |
| 284 |
1
1. build : negated conditional → NO_COVERAGE |
} else if (leftovers.size() == 1) { |
| 285 | subcommand = PresetSubcommand.PLAY; | |
| 286 | this.source = new File(leftovers.getFirst()); | |
| 287 | } else { | |
| 288 | throw new CliException(composeErrMsg(format("Unrecognized arguments:%s", leftovers.subList(2, leftovers.size())), "-")); | |
| 289 | } | |
| 290 | } | |
| 291 |
1
1. build : replaced return value with null for com/github/dakusui/symfonion/cli/Cli$Builder::build → KILLED |
return new Cli(subcommand, source, sink, routeRequest, midiInRegexPatterns, midiOutRegexPatterns, options1, createSymfonion()); |
| 292 | } | |
| 293 | } | |
| 294 | } | |
Mutations | ||
| 65 |
1.1 |
|
| 66 |
1.1 |
|
| 67 |
1.1 |
|
| 68 |
1.1 |
|
| 76 |
1.1 |
|
| 77 |
1.1 |
|
| 78 |
1.1 |
|
| 83 |
1.1 |
|
| 84 |
1.1 |
|
| 85 |
1.1 |
|
| 90 |
1.1 |
|
| 91 |
1.1 |
|
| 94 |
1.1 |
|
| 98 |
1.1 |
|
| 104 |
1.1 |
|
| 123 |
1.1 |
|
| 130 |
1.1 |
|
| 133 |
1.1 |
|
| 136 |
1.1 |
|
| 139 |
1.1 |
|
| 142 |
1.1 |
|
| 145 |
1.1 |
|
| 148 |
1.1 |
|
| 156 |
1.1 2.2 |
|
| 157 |
1.1 |
|
| 160 |
1.1 |
|
| 166 |
1.1 |
|
| 170 |
1.1 |
|
| 172 |
1.1 |
|
| 176 |
1.1 |
|
| 177 |
1.1 |
|
| 178 |
1.1 |
|
| 192 |
1.1 |
|
| 195 |
1.1 |
|
| 197 |
1.1 2.2 |
|
| 198 |
1.1 |
|
| 206 |
1.1 |
|
| 207 |
1.1 |
|
| 214 |
1.1 |
|
| 216 |
1.1 |
|
| 217 |
1.1 |
|
| 219 |
1.1 |
|
| 237 |
1.1 |
|
| 240 |
1.1 |
|
| 243 |
1.1 |
|
| 245 |
1.1 |
|
| 251 |
1.1 2.2 |
|
| 253 |
1.1 2.2 |
|
| 255 |
1.1 2.2 |
|
| 257 |
1.1 2.2 |
|
| 260 |
1.1 |
|
| 264 |
1.1 2.2 |
|
| 267 |
1.1 |
|
| 271 |
1.1 2.2 |
|
| 274 |
1.1 |
|
| 282 |
1.1 |
|
| 284 |
1.1 |
|
| 291 |
1.1 |