Jak przekazać wartość do programu w chwili uruchomienia?

Dotychczasowe przekazywanie wartości do programu

Zgodnie z obietnicą wracam do tematu, który poruszyłem w ostatnim wpisie. Stworzyliśmy program, który potrzebował w czasie uruchomienia trzech wartości:

  • Ścieżki do pliku png ze znakiem wodnym
  • Ścieżki do zdjęcia, do którego chcemy dodać znak wodny
  • Ścieżki dla wynikowego pliku

Oczywiście, ścieżki możemy zahardkodować w programie. To oznacza, że ścieżki te wpiszemy jako wartości zmiennych w naszym programie. Każde uruchomienie dla innego zdjęcia będzie wymagało ponownej kompilacji programu. Na dłuższą metę jest to męczące. Jeżeli chcemy stworzyć program, który będziemy wykorzystać więcej niż raz, takie podejście jest nieakceptowalne.

Rozwiązanie, które zademonstrowałem w poprzednim poście, polegało na wykorzystaniu tablicy Stringów – argumentu metody main. Przy uruchamianiu jakiegokolwiek programu w linii poleceń, wszystkie napisy, które występują po nazwie pliku wykonywalnego są przekazywane do tego programu jako argumenty uruchomienia aplikacji (jest to pewne uproszczenie, bo jest przecież potokowanie, ale o tym napiszę kiedy indziej). Każdy język programowania ma swój sposób obsługi takich argumentów. W Javie jest to argument metody main. Jeżeli uważnie czytałeś poprzedni artykuł, to pewnie zauważyłeś, że wymieniłem ścieżki w innej kolejności niż je obsługiwałem w swoim przykładowym programie. Jest to argument przemawiający na niekorzyść tej metody.

Uruchamianie programu z przełącznikami

I rzeczywiście tak jest. Jeżeli czytasz mój blog regularnie, to już widziałeś, że programy uruchamiane w linii poleceń mają dodatkowe opcje uruchomienia. Weźmy na warsztat program ls, program który listuje zawartość katalogów.

  • komenda ls wypisuje wszystkie pliki w bieżącym folderze
  • komenda ls -a wypisuje wszystkie pliki z plikami ukrytymi włacznie
  • komenda ls -l wypisuje wszystkie pliki w bieżącym folderze w charakterystycznie formie tabelarycznej
  • wszystkie możliwe opcje uruchomienia programu ls możemy wyświetlić za pomocą komendy:
    pawel@DESKTOP-0BB9FUV MINGW64 /
    $ ls --help
    Usage: ls [OPTION]... [FILE]...
    List information about the FILEs (the current directory by default).
    Sort entries alphabetically if none of -cftuvSUX nor --sort is specified.
    
    Mandatory arguments to long options are mandatory for short options too.
      -a, --all                  do not ignore entries starting with .
      -A, --almost-all           do not list implied . and ..
          --author               with -l, print the author of each file
      -b, --escape               print C-style escapes for nongraphic characters
          --block-size=SIZE      with -l, scale sizes by SIZE when printing them;
                                   e.g., '--block-size=M'; see SIZE format below
      -B, --ignore-backups       do not list implied entries ending with ~
      -c                         with -lt: sort by, and show, ctime (time of last
                                   modification of file status information);
                                   with -l: show ctime and sort by name;
                                   otherwise: sort by ctime, newest first
      -C                         list entries by columns
          --color[=WHEN]         colorize the output; WHEN can be 'always' (default
                                   if omitted), 'auto', or 'never'; more info below
      -d, --directory            list directories themselves, not their contents
      -D, --dired                generate output designed for Emacs' dired mode
      -f                         do not sort, enable -aU, disable -ls --color
      -F, --classify             append indicator (one of */=>@|) to entries
          --file-type            likewise, except do not append '*'
          --format=WORD          across -x, commas -m, horizontal -x, long -l,
                                   single-column -1, verbose -l, vertical -C
          --full-time            like -l --time-style=full-iso
      -g                         like -l, but do not list owner
          --group-directories-first
                                 group directories before files;
                                   can be augmented with a --sort option, but any
                                   use of --sort=none (-U) disables grouping
      -G, --no-group             in a long listing, don't print group names
      -h, --human-readable       with -l and -s, print sizes like 1K 234M 2G etc.
          --si                   likewise, but use powers of 1000 not 1024
      -H, --dereference-command-line
                                 follow symbolic links listed on the command line
          --dereference-command-line-symlink-to-dir
                                 follow each command line symbolic link
                                   that points to a directory
          --hide=PATTERN         do not list implied entries matching shell PATTERN
                                   (overridden by -a or -A)
          --hyperlink[=WHEN]     hyperlink file names; WHEN can be 'always'
                                   (default if omitted), 'auto', or 'never'
          --indicator-style=WORD  append indicator with style WORD to entry names:
                                   none (default), slash (-p),
                                   file-type (--file-type), classify (-F)
      -i, --inode                print the index number of each file
      -I, --ignore=PATTERN       do not list implied entries matching shell PATTERN
      -k, --kibibytes            default to 1024-byte blocks for disk usage;
                                   used only with -s and per directory totals
      -l                         use a long listing format
      -L, --dereference          when showing file information for a symbolic
                                   link, show information for the file the link
                                   references rather than for the link itself
      -m                         fill width with a comma separated list of entries
      -n, --numeric-uid-gid      like -l, but list numeric user and group IDs
      -N, --literal              print entry names without quoting
      -o                         like -l, but do not list group information
      -p, --indicator-style=slash
                                 append / indicator to directories
      -q, --hide-control-chars   print ? instead of nongraphic characters
          --show-control-chars   show nongraphic characters as-is (the default,
                                   unless program is 'ls' and output is a terminal)
      -Q, --quote-name           enclose entry names in double quotes
          --quoting-style=WORD   use quoting style WORD for entry names:
                                   literal, locale, shell, shell-always,
                                   shell-escape, shell-escape-always, c, escape
                                   (overrides QUOTING_STYLE environment variable)
      -r, --reverse              reverse order while sorting
      -R, --recursive            list subdirectories recursively
      -s, --size                 print the allocated size of each file, in blocks
      -S                         sort by file size, largest first
          --sort=WORD            sort by WORD instead of name: none (-U), size (-S),
                                   time (-t), version (-v), extension (-X)
          --time=WORD            change the default of using modification times;
                                   access time (-u): atime, access, use;
                                   change time (-c): ctime, status;
                                   birth time: birth, creation;
                                 with -l, WORD determines which time to show;
                                 with --sort=time, sort by WORD (newest first)
          --time-style=TIME_STYLE  time/date format with -l; see TIME_STYLE below
      -t                         sort by time, newest first; see --time
      -T, --tabsize=COLS         assume tab stops at each COLS instead of 8
      -u                         with -lt: sort by, and show, access time;
                                   with -l: show access time and sort by name;
                                   otherwise: sort by access time, newest first
      -U                         do not sort; list entries in directory order
      -v                         natural sort of (version) numbers within text
      -w, --width=COLS           set output width to COLS.  0 means no limit
      -x                         list entries by lines instead of by columns
      -X                         sort alphabetically by entry extension
      -Z, --context              print any security context of each file
      -1                         list one file per line.  Avoid '\n' with -q or -b
          --append-exe           append .exe if cygwin magic was needed
          --help     display this help and exit
          --version  output version information and exit
    

Na co warto zwrócić uwagę? Przede wszystkim na to, że są opcje krótkie i długie. Na przykład komenda ls -a da taki sam rezultat jak ls --all. Widzimy, że niektóre opcje pozwalają przekazać wartość do programu. Na przykład przełącznik -T. Możemy przekazać wartość 4 wywołując komendę ls -T 4 lub równoważnie ls --tabsize=4. Możliwości jest wiele. Pomyśl sobie, jak wygodnie byłoby wywołać program do dodawania znaków wodnych w następujący sposób:

pawel@DESKTOP-0BB9FUV MINGW64 ~/OneDrive/Pictures/picts
$ watermark.sh -i IMG_0601.JPG -o mak.jpg -w watermarkWhite.png

zamiast tego, co pokazałem w poprzednim wpisie:

pawel@DESKTOP-0BB9FUV MINGW64 ~/OneDrive/Pictures/picts
$ watermark.sh IMG_0601.JPG watermarkWhite.png mak.jpg

Nie musisz pamiętać o kolejności.

Jest na to kilka sposobów. Dziś pokażę jeden z nich, który dodatkowo pomoże w szybki sposób wygenerować instrukcję użycia naszej aplikacji.

Na szybko znalazałem bardzo fajny post na stronie stackoverflow. Autor tego wpisu sugeruje przyjrzenie się trzem bibliotekom. Z tych trzech zainteresowały mnie dwie. Dziś przyjrzymy się apache-owej bibliotece CLI. Natomiast w najbliższej przyszłości przyjrzymy się bibliotece picocli. Ale najpierw chciałbym napisać artykuł o annotatacjach (to jest kalka z angielskiego, wydaje mi się, że nazywanie tego po polsku adnotacjami nie jest błędne).

Biblioteka Commons CLI

O organizacji Apache, już wspominałem we wpisie o Mavenie. Ta organizacja zajmuje się również tworzeniem bibliotek, które są udostępniane jako oprogramowanie open source – czyli możesz takie biblioteki użyć w swoim programie i następnie ten program sprzedawać. Ale uważaj – niektóre licencje open source wymagają aby wszelkie oprogramowanie wykorzystujące daną bibliotekę również były udostępniane na takiej samej zasadzie (innymi słowy wyłączają możliwość zarabiania na takiej aplikacji).

Bazując na danych ze strony projektu CLI oraz na repozytorium Mavena, najnowsza wersja biblioteki CLI to 1.4, która była wydana dość dawno. Ale na potrzeby tego wpisu zostańmy przy tym

Na potrzeby niniejszego wpisu stworzyłem nowy projekt Mavenowy, który jest dostępny na moim githubie. W pliku pom.xml mamy dwie zależności, oraz zdefiniowanie plug-inu do budowania kompletnej paczki jar:

<?xml version="1.0" encoding="UTF-8"?>

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>

  <groupId>pl.backlog.green</groupId>
  <artifactId>pictures-manipulation-4</artifactId>
  <version>1.0-SNAPSHOT</version>

  <name>pictures-manipulation-4</name>
  <url>http://zielony-backlog.pl</url>

  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <maven.compiler.source>14</maven.compiler.source>
    <maven.compiler.target>14</maven.compiler.target>
  </properties>

  <dependencies>
    <dependency>
      <groupId>org.openpnp</groupId>
      <artifactId>opencv</artifactId>
      <version>4.3.0-2</version>
    </dependency>
    <dependency>
      <groupId>commons-cli</groupId>
      <artifactId>commons-cli</artifactId>
      <version>1.4</version>
    </dependency>

  </dependencies>

  <build>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-assembly-plugin</artifactId>
        <executions>
          <execution>
            <phase>package</phase>
            <goals>
              <goal>single</goal>
            </goals>
            <configuration>
              <descriptorRefs>
                <descriptorRef>jar-with-dependencies</descriptorRef>
              </descriptorRefs>
            </configuration>
          </execution>
        </executions>
      </plugin>
    </plugins>
  </build>
</project>

W tym momencie nie przejmujemy się klasą Watermark. Do niej przejdziemy na sam koniec tego wpisu. W dalszej części posta będziemy poznawać kolejne elementy biblioteki. W kolejnych etapach, będziemy kompilowali i uruchamiali kolejne proste klasy.

Test01 – prosta opcja bez wartości

Zacznijmy od klasy Test01, która pokazuje w jaki sposób możemy zdefiniować prostą flagę uruchomieniową:

public class Test01 {
    public static void main(String[] args) {
        Option option = new Option("a", "first option without value, not mandatory");

        Options options = new Options();
        options.addOption(option);

        CommandLineParser parser = new DefaultParser();
        HelpFormatter help = new HelpFormatter();
        CommandLine cmd = null;

        try {
            cmd = parser.parse(options, args);
        } catch (ParseException e) {
            System.out.println(e.getMessage());
            help.printHelp("Test01", options);
            System.exit(1);
        }

        if (cmd.hasOption("a")) {
            System.out.println("Podałeś opcję 'a'");
        } else {
            System.out.println("Nie podałeś opcji 'a'");
        }
    }
}

Widzimy, że wejściowa tablica Stringów jest parsowana do poszczególnych opcji zdefiniowanych w obiekcie parsera. Dodatkowo, właściwie za darmo, dostajemy wydruk instrukcji użycia naszej klasy. Oczywiście program kompilujemy:

pawel@DESKTOP-0BB9FUV MINGW64 ~/code/pictures-manipulation-4 (master)
$ mvn clean package

I następnie uruchamiamy w kilku konfiguracjach:

pawel@DESKTOP-0BB9FUV MINGW64 ~/code/pictures-manipulation-4 (master)
$ java -classpath "target/pictures-manipulation-4-1.0-SNAPSHOT-jar-with-dependencies.jar" pl.backlog.green.Test01
Nie podałeś opcji 'a'

pawel@DESKTOP-0BB9FUV MINGW64 ~/code/pictures-manipulation-4 (master)
$ java -classpath "target/pictures-manipulation-4-1.0-SNAPSHOT-jar-with-dependencies.jar" pl.backlog.green.Test01 -a
Podałeś opcję 'a'

pawel@DESKTOP-0BB9FUV MINGW64 ~/code/pictures-manipulation-4 (master)
$ java -classpath "target/pictures-manipulation-4-1.0-SNAPSHOT-jar-with-dependencies.jar" pl.backlog.green.Test01 -b
Unrecognized option: -b
usage: Test01
 -a   first option without value, not mandatory

Widzimy, że linijka

Option option = new Option("a", "first option without value, not mandatory");

definiuje nam krótką opcję, dla której od razu dodajemy opis.

Test02 – opcja z wartością

Widzieliśmy w akcji dwuargumentowy konstruktor klasy Option. Teraz pora na trójargumentowy. Dochodzi parametr boolean, który definiuje czy dana opcja uruchomienia posiada wartość, czy nie.

public class Test02 {
    public static void main(String[] args) {
        Options options = new Options();

        options.addOption(new Option("a", "first option without value, not mandatory"));
        options.addOption(new Option("b", false,"second option without value, not mandatory"));
        options.addOption(new Option("c", true,"third option with value, not mandatory"));

        CommandLineParser parser = new DefaultParser();
        HelpFormatter help = new HelpFormatter();
        CommandLine cmd = null;

        try {
            cmd = parser.parse(options, args);
        } catch (ParseException e) {
            System.out.println(e.getMessage());
            help.printHelp("Test01", options);
            System.exit(1);
        }

        if (cmd.hasOption("a")) {
            System.out.println("Podałeś opcję 'a'");
        } else {
            System.out.println("Nie podałeś opcji 'a'");
        }

        if (cmd.hasOption("b")) {
            System.out.println("Podałeś opcję 'b'");
        } else {
            System.out.println("Nie podałeś opcji 'b'");
        }

        if (cmd.hasOption("c")) {
            String value = cmd.getOptionValue('c');
            System.out.println("Podałeś opcję 'c' z wartością " + value);
        } else {
            System.out.println("Nie podałeś opcji 'c'");
        }
    }
}

I teraz możemy uruchomić ten przykład dla kilku argumentów:

pawel@DESKTOP-0BB9FUV MINGW64 ~/code/pictures-manipulation-4 (master)
$ java -classpath "target/pictures-manipulation-4-1.0-SNAPSHOT-jar-with-dependencies.jar" pl.backlog.green.Test02 -c val
Nie podałeś opcji 'a'
Nie podałeś opcji 'b'
Podałeś opcję 'c' z wartością val

pawel@DESKTOP-0BB9FUV MINGW64 ~/code/pictures-manipulation-4 (master)
$ java -classpath "target/pictures-manipulation-4-1.0-SNAPSHOT-jar-with-dependencies.jar" pl.backlog.green.Test02 -c -a val
Missing argument for option: c
usage: Test01
 -a         first option without value, not mandatory
 -b         second option without value, not mandatory
 -c <arg>   third option with value, not mandatory

pawel@DESKTOP-0BB9FUV MINGW64 ~/code/pictures-manipulation-4 (master)
$ java -classpath "target/pictures-manipulation-4-1.0-SNAPSHOT-jar-with-dependencies.jar" pl.backlog.green.Test02 -c val -b
Nie podałeś opcji 'a'
Podałeś opcję 'b'
Podałeś opcję 'c' z wartością val

Test03 – opcja w wersji długiej

No to przejdźmy do czteroargumentowego konstruktora. Ten dodatkowy argument odpowiada za długi opis opcji uruchomienia.

public class Test03 {
    public static void main(String[] args) {
        Options options = new Options();

        options.addOption(new Option("a", "first option without value, not mandatory"));
        options.addOption(new Option("b", false, "second option without value, not mandatory"));
        options.addOption(new Option("c", true, "third option with value, not mandatory"));
        options.addOption(new Option("h", "help", false, "prints usage help"));

        CommandLineParser parser = new DefaultParser();
        HelpFormatter help = new HelpFormatter();
        CommandLine cmd = null;

        try {
            cmd = parser.parse(options, args);
        } catch (ParseException e) {
            System.out.println(e.getMessage());
            help.printHelp("Test03", options);
            System.exit(1);
        }

        if (cmd.hasOption("h")) {
            help.printHelp("Test03", options);
            System.exit(0);
        }

        if (cmd.hasOption("a")) {
            System.out.println("Podałeś opcję 'a'");
        } else {
            System.out.println("Nie podałeś opcji 'a'");
        }

        if (cmd.hasOption("b")) {
            System.out.println("Podałeś opcję 'b'");
        } else {
            System.out.println("Nie podałeś opcji 'b'");
        }

        if (cmd.hasOption("c")) {
            String value = cmd.getOptionValue('c');
            System.out.println("Podałeś opcję 'c' z wartością " + value);
        } else {
            System.out.println("Nie podałeś opcji 'c'");
        }
    }
}

I zobaczmy, jak można wykorzystać opcję pomocy:

pawel@DESKTOP-0BB9FUV MINGW64 ~/code/pictures-manipulation-4 (master)
$ java -classpath "target/pictures-manipulation-4-1.0-SNAPSHOT-jar-with-dependencies.jar" pl.backlog.green.Test03 -h
usage: Test03
 -a          first option without value, not mandatory
 -b          second option without value, not mandatory
 -c <arg>    third option with value, not mandatory
 -h,--help   prints usage help

pawel@DESKTOP-0BB9FUV MINGW64 ~/code/pictures-manipulation-4 (master)
$ java -classpath "target/pictures-manipulation-4-1.0-SNAPSHOT-jar-with-dependencies.jar" pl.backlog.green.Test03 --help
usage: Test03
 -a          first option without value, not mandatory
 -b          second option without value, not mandatory
 -c <arg>    third option with value, not mandatory
 -h,--help   prints usage help

Test04 – opcja obowiązkowa

Opcję możemy ustawić jako wymaganą. Nie ma do tego odpowiedniego konstruktora, musimy wywołać odpowiednią metodę na obiekcie klasy Option:

public class Test04 {
    public static void main(String[] args) {
        Option option = new Option("a", "first option without value, mandatory");
        option.setRequired(true);

        Options options = new Options();
        options.addOption(option);

        CommandLineParser parser = new DefaultParser();
        HelpFormatter help = new HelpFormatter();
        CommandLine cmd = null;

        try {
            cmd = parser.parse(options, args);
        } catch (ParseException e) {
            System.out.println(e.getMessage());
            help.printHelp("Test04", options);
            System.exit(1);
        }

        if (cmd.hasOption("a")) {
            System.out.println("Podałeś opcję 'a'");
        }
    }
}

I jeżeli nie podamy opcji a, to program zakończy się wypisaniem odpowiedniego komunikatu:

pawel@DESKTOP-0BB9FUV MINGW64 ~/code/pictures-manipulation-4 (master)
$ java -classpath "target/pictures-manipulation-4-1.0-SNAPSHOT-jar-with-dependencies.jar" pl.backlog.green.Test04
Missing required option: a
usage: Test04
 -a   first option without value, mandatory

pawel@DESKTOP-0BB9FUV MINGW64 ~/code/pictures-manipulation-4 (master)
$ java -classpath "target/pictures-manipulation-4-1.0-SNAPSHOT-jar-with-dependencies.jar" pl.backlog.green.Test04 -a
Podałeś opcję 'a'

Test05 – dodatkowe argumenty programu

Być może zwróciłeś uwagę, ale program ls oprócz przełączników może przyjąć jeszcze argument nie pasujący do żadnej opcji uruchomienia. To znaczy, że możemy wylistować zawartość dowolnego katalogu, nie tylko bieżącego:

pawel@DESKTOP-0BB9FUV MINGW64 ~/code/pictures-manipulation-4 (master)
$ ls /c/Users/pawel/code/
my-app  my-library  pictures-manipulation-4

My również możemy dostać się do tych argumentów, które nie zostały dopasowane do żadnego przełącznika. W klasie CommandLine jest metoda getArgList(), która właśnie zwraca listę takich argumentów. Najpierw kod kolejnej klasy:

public class Test05 {
    public static void main(String[] args) {
        Option option = new Option("a", "first option without value, mandatory");
        option.setRequired(true);

        Options options = new Options();
        options.addOption(option);

        CommandLineParser parser = new DefaultParser();
        HelpFormatter help = new HelpFormatter();
        CommandLine cmd = null;

        try {
            cmd = parser.parse(options, args);
        } catch (ParseException e) {
            System.out.println(e.getMessage());
            help.printHelp("Test05 [OPTIONS] ARG", options);
            System.exit(1);
        }

        if (cmd.hasOption("a")) {
            System.out.println("Podałeś opcję 'a'");
        }

        System.out.println(cmd.getArgList());
    }
}

I teraz przykłady uruchomienia:

pawel@DESKTOP-0BB9FUV MINGW64 ~/code/pictures-manipulation-4 (master)
$ java -classpath "target/pictures-manipulation-4-1.0-SNAPSHOT-jar-with-dependencies.jar" pl.backlog.green.Test05  -a
Podałeś opcję 'a'
[]

pawel@DESKTOP-0BB9FUV MINGW64 ~/code/pictures-manipulation-4 (master)
$ java -classpath "target/pictures-manipulation-4-1.0-SNAPSHOT-jar-with-dependencies.jar" pl.backlog.green.Test05  -a  VAL
Podałeś opcję 'a'
[VAL]

pawel@DESKTOP-0BB9FUV MINGW64 ~/code/pictures-manipulation-4 (master)
$ java -classpath "target/pictures-manipulation-4-1.0-SNAPSHOT-jar-with-dependencies.jar" pl.backlog.green.Test05 BLA  -a  VAL
Podałeś opcję 'a'
[BLA, VAL]

Wydaje mi się, że takie informacje są wystarczające do rozpoczęcia używania tej biblioteki w praktyce.

Nowa wersja programu Watermark

Zanim przejdziesz dalej, spróbuj sam się zastanowić, jak możemy dodać parsowanie argumentów uruchomienia do programu Watermark. Oczywiście moje rozwiazanie znajduje się poniżej:

public class Watermak {

    private static Mat addWatermarkToImage(Mat image, Mat watermark) {
        Size inSize = image.size();
        Size outSize = new Size(inSize.width/2.5, inSize.height/2.5);

        Mat scaledImage = new Mat();
        resize(image, scaledImage, outSize);
        cvtColor(scaledImage, scaledImage, COLOR_BGR2BGRA);

        Mat transparentLayer = new Mat(outSize, CvType.CV_8UC4);
        Rect roi = new Rect(
                scaledImage.cols() - watermark.cols() - 10,
                scaledImage.rows() - watermark.rows() - 10,
                watermark.cols(),
                watermark.rows());
        watermark.copyTo(transparentLayer.submat(roi));

        Mat result = new Mat();
        addWeighted(scaledImage, 1, transparentLayer, 0.35, 0, result);

        return result;
    }

    private static void performAction(String imagePath, String watermarkPath, String outputImagePath) {
        Mat image = Imgcodecs.imread(imagePath, IMREAD_COLOR);
        Mat watermark = imread(watermarkPath, IMREAD_UNCHANGED);
        imwrite(outputImagePath, addWatermarkToImage(image, watermark));
    }

    public static void main(String[] args) {
        nu.pattern.OpenCV.loadLocally();

        Options options = new Options();

        Option input = new Option("i", "input", true, "path to input image");
        input.setRequired(true);
        options.addOption(input);

        Option output = new Option("o", "output", true, "path to outpur image");
        output.setRequired(true);
        options.addOption(output);

        Option watermark = new Option("w", "watermark", true, "path to watermark image");
        watermark.setRequired(true);
        options.addOption(watermark);

        Option help = new Option("h", "help", false, "prints this message");
        options.addOption(help);


        CommandLineParser parser = new DefaultParser();
        HelpFormatter helpFormatter = new HelpFormatter();
        CommandLine cmd = null;

        try {
            cmd = parser.parse(options, args);
        } catch (ParseException e) {
            System.out.println(e.getMessage());
            helpFormatter.printHelp("watermark.sh [OPTIONS]", options);
            System.exit(1);
        }
        performAction(cmd.getOptionValue("i"), cmd.getOptionValue("w"), cmd.getOptionValue("o"));
    }
}

Teraz możemy zbudować projekt:

pawel@DESKTOP-0BB9FUV MINGW64 ~/code/pictures-manipulation-4 (master)
$ mvn clean package

Skopiować go do katalogu ~/bin:

pawel@DESKTOP-0BB9FUV MINGW64 ~/code/pictures-manipulation-4 (master)
$ cp target/pictures-manipulation-4-1.0-SNAPSHOT-jar-with-dependencies.jar /c/Users/pawel/bin/pictures-manipulation-4.jar

Zaktualizować zawartość pliku ~/bin/watermark.sh:

#!/bin/bash

java -jar /c/Users/pawel/bin/pictures-manipulation-4.jar $@

I teraz możemy uruchomić nasz program, na przykład w taki sposób:

pawel@DESKTOP-0BB9FUV MINGW64 ~/OneDrive/Pictures/picts
$ watermark.sh --input=IMG_0601.JPG -o mak.jpg --watermark=watermarkWhite.png

Podsumowanie

Pokazałem ideę uruchamiania programu z wygodnym przekazaniem wartości w momencie uruchamiania programu. Oczywiście, można również korzystać z plików konfiguracyjnych, ale o tym kiedyś indziej. Dziś poznaliśmy dość tradycyjną (albo mniej delikatnie – starą) bibliotekę Commons CLI od Apache. W następnym wpisie przedstawię Javowe adnotacje, żebyśmy wszyscy z podobną wiedzą przeszli do biblioteki picocoli – a to wszystko już wkrótce.

 

Aspirujący twórca internetowy, który zna się na programowaniu i chce się dzielić wiedzą

Dodaj komentarz

Twój adres email nie zostanie opublikowany. Pola, których wypełnienie jest wymagane, są oznaczone symbolem *

Scroll to top