diff --git a/libraries-3/README.md b/libraries-3/README.md
new file mode 100644
index 0000000000..a6c6b190ab
--- /dev/null
+++ b/libraries-3/README.md
@@ -0,0 +1,9 @@
+## Libraries-3
+
+This module contains articles about various Java libraries.
+These are small libraries that are relatively easy to use and do not require any separate module of their own.
+
+The code examples related to different libraries are each in their own module.
+
+Remember, for advanced libraries like [Jackson](/jackson) and [JUnit](/testing-modules) we already have separate modules. Please make sure to have a look at the existing modules in such cases.
+
diff --git a/libraries-3/pom.xml b/libraries-3/pom.xml
new file mode 100644
index 0000000000..214e87287d
--- /dev/null
+++ b/libraries-3/pom.xml
@@ -0,0 +1,51 @@
+
+
+ 4.0.0
+ libraries-3
+ libraries-3
+
+
+ com.baeldung
+ parent-modules
+ 1.0.0-SNAPSHOT
+
+
+
+
+ jboss-public-repository-group
+ JBoss Public Repository Group
+ http://repository.jboss.org/nexus/content/groups/public/
+
+ true
+ never
+
+
+ true
+ daily
+
+
+
+
+
+
+ com.beust
+ jcommander
+ ${jcommander.version}
+
+
+ org.projectlombok
+ lombok
+ ${lombok.version}
+
+
+
+
+ 1.78
+ 1.18.6
+ UTF-8
+ 1.8
+ 1.8
+
+
diff --git a/libraries-3/src/main/java/com/baeldung/jcommander/helloworld/HelloWorldApp.java b/libraries-3/src/main/java/com/baeldung/jcommander/helloworld/HelloWorldApp.java
new file mode 100644
index 0000000000..56a74ecf21
--- /dev/null
+++ b/libraries-3/src/main/java/com/baeldung/jcommander/helloworld/HelloWorldApp.java
@@ -0,0 +1,37 @@
+package com.baeldung.jcommander.helloworld;
+
+import com.beust.jcommander.JCommander;
+import com.beust.jcommander.Parameter;
+
+public class HelloWorldApp {
+
+ /*
+ * Execute:
+ * mvn exec:java -Dexec.mainClass=com.baeldung.jcommander.helloworld.HelloWorldApp -q \
+ * -Dexec.args="--name JavaWorld"
+ */
+ public static void main(String[] args) {
+ HelloWorldArgs jArgs = new HelloWorldArgs();
+ JCommander helloCmd = JCommander
+ .newBuilder()
+ .addObject(jArgs)
+ .build();
+
+ helloCmd.parse(args);
+ System.out.println("Hello " + jArgs.getName());
+ }
+}
+
+class HelloWorldArgs {
+
+ @Parameter(
+ names = "--name",
+ description = "User name",
+ required = true
+ )
+ private String name;
+
+ public String getName() {
+ return name;
+ }
+}
diff --git a/libraries-3/src/main/java/com/baeldung/jcommander/usagebilling/UsageBasedBillingApp.java b/libraries-3/src/main/java/com/baeldung/jcommander/usagebilling/UsageBasedBillingApp.java
new file mode 100644
index 0000000000..029e7eed01
--- /dev/null
+++ b/libraries-3/src/main/java/com/baeldung/jcommander/usagebilling/UsageBasedBillingApp.java
@@ -0,0 +1,23 @@
+package com.baeldung.jcommander.usagebilling;
+
+import com.baeldung.jcommander.usagebilling.cli.UsageBasedBilling;
+
+public class UsageBasedBillingApp {
+
+ /*
+ * Entry-point: invokes the cli passing the command-line args
+ *
+ * Invoking "Submit" sub-command:
+ * mvn exec:java \
+ -Dexec.mainClass=com.baeldung.jcommander.usagebilling.UsageBasedBillingApp -q \
+ -Dexec.args="submit --customer cb898e7a-f2a0-46d2-9a09-531f1cee1839 --subscription subscriptionPQRMN001 --pricing-type PRE_RATED --timestamp 2019-10-03T10:58:00 --quantity 7 --price 24.56"
+ *
+ * Invoking "Fetch" sub-command:
+ * mvn exec:java \
+ -Dexec.mainClass=com.baeldung.jcommander.usagebilling.UsageBasedBillingApp -q \
+ -Dexec.args="fetch --customer cb898e7a-f2a0-46d2-9a09-531f1cee1839 --subscription subscriptionPQRMN001 subscriptionPQRMN002 subscriptionPQRMN003 --itemized"
+ */
+ public static void main(String[] args) {
+ new UsageBasedBilling().run(args);
+ }
+}
diff --git a/libraries-3/src/main/java/com/baeldung/jcommander/usagebilling/cli/FetchCurrentChargesCommand.java b/libraries-3/src/main/java/com/baeldung/jcommander/usagebilling/cli/FetchCurrentChargesCommand.java
new file mode 100644
index 0000000000..ea6b18af53
--- /dev/null
+++ b/libraries-3/src/main/java/com/baeldung/jcommander/usagebilling/cli/FetchCurrentChargesCommand.java
@@ -0,0 +1,67 @@
+package com.baeldung.jcommander.usagebilling.cli;
+
+import com.baeldung.jcommander.usagebilling.cli.splitter.ColonParameterSplitter;
+import com.baeldung.jcommander.usagebilling.cli.validator.UUIDValidator;
+import com.baeldung.jcommander.usagebilling.model.CurrentChargesRequest;
+import com.baeldung.jcommander.usagebilling.model.CurrentChargesResponse;
+import com.baeldung.jcommander.usagebilling.service.FetchCurrentChargesService;
+import com.beust.jcommander.Parameter;
+import com.beust.jcommander.Parameters;
+import lombok.Getter;
+
+import java.util.List;
+
+import static com.baeldung.jcommander.usagebilling.cli.UsageBasedBilling.*;
+import static com.baeldung.jcommander.usagebilling.service.FetchCurrentChargesService.getDefault;
+
+@Parameters(
+ commandNames = { FETCH_CMD },
+ commandDescription = "Fetch charges for a customer in the current month, can be itemized or aggregated"
+)
+@Getter
+class FetchCurrentChargesCommand {
+
+ FetchCurrentChargesCommand() {
+ }
+
+ private FetchCurrentChargesService service = getDefault();
+
+ @Parameter(names = "--help", help = true)
+ private boolean help;
+
+ @Parameter(
+ names = { "--customer", "-C" },
+ description = "Id of the Customer who's using the services",
+ validateWith = UUIDValidator.class,
+ order = 1,
+ required = true
+ )
+ private String customerId;
+
+ @Parameter(
+ names = { "--subscription", "-S" },
+ description = "Filter charges for specific subscription Ids, includes all subscriptions if no value is specified",
+ variableArity = true,
+ splitter = ColonParameterSplitter.class,
+ order = 2
+ )
+ private List subscriptionIds;
+
+ @Parameter(
+ names = { "--itemized" },
+ description = "Whether the response should contain breakdown by subscription, only aggregate values are returned by default",
+ order = 3
+ )
+ private boolean itemized;
+
+ void fetch() {
+ CurrentChargesRequest req = CurrentChargesRequest.builder()
+ .customerId(customerId)
+ .subscriptionIds(subscriptionIds)
+ .itemized(itemized)
+ .build();
+
+ CurrentChargesResponse response = service.fetch(req);
+ System.out.println(response);
+ }
+}
diff --git a/libraries-3/src/main/java/com/baeldung/jcommander/usagebilling/cli/SubmitUsageCommand.java b/libraries-3/src/main/java/com/baeldung/jcommander/usagebilling/cli/SubmitUsageCommand.java
new file mode 100644
index 0000000000..d3516b19d3
--- /dev/null
+++ b/libraries-3/src/main/java/com/baeldung/jcommander/usagebilling/cli/SubmitUsageCommand.java
@@ -0,0 +1,96 @@
+package com.baeldung.jcommander.usagebilling.cli;
+
+import com.baeldung.jcommander.usagebilling.cli.converter.ISO8601TimestampConverter;
+import com.baeldung.jcommander.usagebilling.cli.validator.UUIDValidator;
+import com.baeldung.jcommander.usagebilling.model.UsageRequest;
+import com.baeldung.jcommander.usagebilling.model.UsageRequest.PricingType;
+import com.baeldung.jcommander.usagebilling.service.SubmitUsageService;
+import com.beust.jcommander.Parameter;
+import com.beust.jcommander.Parameters;
+import lombok.Getter;
+
+import java.math.BigDecimal;
+import java.time.Instant;
+
+import static com.baeldung.jcommander.usagebilling.cli.UsageBasedBilling.*;
+import static com.baeldung.jcommander.usagebilling.service.SubmitUsageService.getDefault;
+
+@Parameters(
+ commandNames = { SUBMIT_CMD },
+ commandDescription = "Submit usage for a given customer and subscription, accepts one usage item"
+)
+@Getter
+class SubmitUsageCommand {
+
+ SubmitUsageCommand() {
+ }
+
+ private SubmitUsageService service = getDefault();
+
+ @Parameter(names = "--help", help = true)
+ private boolean help;
+
+ @Parameter(
+ names = { "--customer", "-C" },
+ description = "Id of the Customer who's using the services",
+ validateWith = UUIDValidator.class,
+ order = 1,
+ required = true
+ )
+ private String customerId;
+
+ @Parameter(
+ names = { "--subscription", "-S" },
+ description = "Id of the Subscription that was purchased",
+ order = 2,
+ required = true
+ )
+ private String subscriptionId;
+
+ @Parameter(
+ names = { "--pricing-type", "-P" },
+ description = "Pricing type of the usage reported",
+ order = 3,
+ required = true
+ )
+ private PricingType pricingType;
+
+ @Parameter(
+ names = { "--quantity" },
+ description = "Used quantity; reported quantity is added over the billing period",
+ order = 3,
+ required = true
+ )
+ private Integer quantity;
+
+ @Parameter(
+ names = { "--timestamp" },
+ description = "Timestamp of the usage event, must lie in the current billing period",
+ converter = ISO8601TimestampConverter.class,
+ order = 4,
+ required = true
+ )
+ private Instant timestamp;
+
+ @Parameter(
+ names = { "--price" },
+ description = "If PRE_RATED, unit price to be applied per unit of usage quantity reported",
+ order = 5
+ )
+ private BigDecimal price;
+
+ void submit() {
+
+ UsageRequest req = UsageRequest.builder()
+ .customerId(customerId)
+ .subscriptionId(subscriptionId)
+ .pricingType(pricingType)
+ .quantity(quantity)
+ .timestamp(timestamp)
+ .price(price)
+ .build();
+
+ String reqId = service.submit(req);
+ System.out.println("Generated Request Id for reference: " + reqId);
+ }
+}
diff --git a/libraries-3/src/main/java/com/baeldung/jcommander/usagebilling/cli/UsageBasedBilling.java b/libraries-3/src/main/java/com/baeldung/jcommander/usagebilling/cli/UsageBasedBilling.java
new file mode 100644
index 0000000000..a531a68644
--- /dev/null
+++ b/libraries-3/src/main/java/com/baeldung/jcommander/usagebilling/cli/UsageBasedBilling.java
@@ -0,0 +1,80 @@
+package com.baeldung.jcommander.usagebilling.cli;
+
+import com.beust.jcommander.JCommander;
+import com.beust.jcommander.ParameterException;
+import com.beust.jcommander.UnixStyleUsageFormatter;
+
+public class UsageBasedBilling {
+
+ static final String SUBMIT_CMD = "submit";
+ static final String FETCH_CMD = "fetch";
+
+ private JCommander jCommander;
+ private SubmitUsageCommand submitUsageCmd;
+ private FetchCurrentChargesCommand fetchChargesCmd;
+
+ public UsageBasedBilling() {
+ this.submitUsageCmd = new SubmitUsageCommand();
+ this.fetchChargesCmd = new FetchCurrentChargesCommand();
+ jCommander = JCommander.newBuilder()
+ .addObject(this)
+ .addCommand(submitUsageCmd)
+ .addCommand(fetchChargesCmd)
+ .build();
+
+ setUsageFormatter(SUBMIT_CMD);
+ setUsageFormatter(FETCH_CMD);
+ }
+
+ public void run(String[] args) {
+ String parsedCmdStr;
+ try {
+ jCommander.parse(args);
+ parsedCmdStr = jCommander.getParsedCommand();
+
+ switch (parsedCmdStr) {
+ case SUBMIT_CMD:
+ if (submitUsageCmd.isHelp()) {
+ getSubCommandHandle(SUBMIT_CMD).usage();
+ }
+ System.out.println("Parsing usage request...");
+ submitUsageCmd.submit();
+ break;
+
+ case FETCH_CMD:
+ if (fetchChargesCmd.isHelp()) {
+ getSubCommandHandle(SUBMIT_CMD).usage();
+ }
+ System.out.println("Preparing fetch query...");
+ fetchChargesCmd.fetch();
+
+ break;
+
+ default:
+ System.err.println("Invalid command: " + parsedCmdStr);
+ }
+ } catch (ParameterException e) {
+ System.err.println(e.getLocalizedMessage());
+ parsedCmdStr = jCommander.getParsedCommand();
+ if (parsedCmdStr != null) {
+ getSubCommandHandle(parsedCmdStr).usage();
+ } else {
+ jCommander.usage();
+ }
+ }
+ }
+
+ private JCommander getSubCommandHandle(String command) {
+ JCommander cmd = jCommander.getCommands().get(command);
+
+ if (cmd == null) {
+ System.err.println("Invalid command: " + command);
+ }
+ return cmd;
+ }
+
+ private void setUsageFormatter(String subCommand) {
+ JCommander cmd = getSubCommandHandle(subCommand);
+ cmd.setUsageFormatter(new UnixStyleUsageFormatter(cmd));
+ }
+}
diff --git a/libraries-3/src/main/java/com/baeldung/jcommander/usagebilling/cli/converter/ISO8601TimestampConverter.java b/libraries-3/src/main/java/com/baeldung/jcommander/usagebilling/cli/converter/ISO8601TimestampConverter.java
new file mode 100644
index 0000000000..e54865a811
--- /dev/null
+++ b/libraries-3/src/main/java/com/baeldung/jcommander/usagebilling/cli/converter/ISO8601TimestampConverter.java
@@ -0,0 +1,33 @@
+package com.baeldung.jcommander.usagebilling.cli.converter;
+
+import com.beust.jcommander.ParameterException;
+import com.beust.jcommander.converters.BaseConverter;
+
+import java.time.Instant;
+import java.time.LocalDateTime;
+import java.time.ZoneOffset;
+import java.time.format.DateTimeFormatter;
+import java.time.format.DateTimeParseException;
+
+import static java.lang.String.format;
+
+public class ISO8601TimestampConverter extends BaseConverter {
+
+ private static final DateTimeFormatter TS_FORMATTER = DateTimeFormatter.ofPattern("uuuu-MM-dd'T'HH:mm:ss");
+
+ public ISO8601TimestampConverter(String optionName) {
+ super(optionName);
+ }
+
+ @Override
+ public Instant convert(String value) {
+ try {
+ return LocalDateTime
+ .parse(value, TS_FORMATTER)
+ .atOffset(ZoneOffset.UTC)
+ .toInstant();
+ } catch (DateTimeParseException e) {
+ throw new ParameterException(getErrorString(value, format("an ISO-8601 formatted timestamp (%s)", TS_FORMATTER.toString())));
+ }
+ }
+}
diff --git a/libraries-3/src/main/java/com/baeldung/jcommander/usagebilling/cli/splitter/ColonParameterSplitter.java b/libraries-3/src/main/java/com/baeldung/jcommander/usagebilling/cli/splitter/ColonParameterSplitter.java
new file mode 100644
index 0000000000..f24c028123
--- /dev/null
+++ b/libraries-3/src/main/java/com/baeldung/jcommander/usagebilling/cli/splitter/ColonParameterSplitter.java
@@ -0,0 +1,15 @@
+package com.baeldung.jcommander.usagebilling.cli.splitter;
+
+import com.beust.jcommander.converters.IParameterSplitter;
+
+import java.util.List;
+
+import static java.util.Arrays.asList;
+
+public class ColonParameterSplitter implements IParameterSplitter {
+
+ @Override
+ public List split(String value) {
+ return asList(value.split(":"));
+ }
+}
diff --git a/libraries-3/src/main/java/com/baeldung/jcommander/usagebilling/cli/validator/UUIDValidator.java b/libraries-3/src/main/java/com/baeldung/jcommander/usagebilling/cli/validator/UUIDValidator.java
new file mode 100644
index 0000000000..a72912f7d0
--- /dev/null
+++ b/libraries-3/src/main/java/com/baeldung/jcommander/usagebilling/cli/validator/UUIDValidator.java
@@ -0,0 +1,26 @@
+package com.baeldung.jcommander.usagebilling.cli.validator;
+
+import com.beust.jcommander.IParameterValidator;
+import com.beust.jcommander.ParameterException;
+
+import java.util.regex.Pattern;
+
+public class UUIDValidator implements IParameterValidator {
+
+ private static final String UUID_REGEX =
+ "[0-9a-fA-F]{8}(-[0-9a-fA-F]{4}){3}-[0-9a-fA-F]{12}";
+
+ @Override
+ public void validate(String name, String value) throws ParameterException {
+ if (!isValidUUID(value)) {
+ throw new ParameterException(
+ "String parameter " + value + " is not a valid UUID.");
+ }
+ }
+
+ private boolean isValidUUID(String value) {
+ return Pattern
+ .compile(UUID_REGEX)
+ .matcher(value).matches();
+ }
+}
diff --git a/libraries-3/src/main/java/com/baeldung/jcommander/usagebilling/model/CurrentChargesRequest.java b/libraries-3/src/main/java/com/baeldung/jcommander/usagebilling/model/CurrentChargesRequest.java
new file mode 100644
index 0000000000..93dd7a5732
--- /dev/null
+++ b/libraries-3/src/main/java/com/baeldung/jcommander/usagebilling/model/CurrentChargesRequest.java
@@ -0,0 +1,16 @@
+package com.baeldung.jcommander.usagebilling.model;
+
+import lombok.*;
+
+import java.util.List;
+
+@NoArgsConstructor(access = AccessLevel.PACKAGE)
+@AllArgsConstructor(access = AccessLevel.PACKAGE)
+@Builder
+@Getter
+public class CurrentChargesRequest {
+
+ private String customerId;
+ private List subscriptionIds;
+ private boolean itemized;
+}
diff --git a/libraries-3/src/main/java/com/baeldung/jcommander/usagebilling/model/CurrentChargesResponse.java b/libraries-3/src/main/java/com/baeldung/jcommander/usagebilling/model/CurrentChargesResponse.java
new file mode 100644
index 0000000000..865a6e4a3d
--- /dev/null
+++ b/libraries-3/src/main/java/com/baeldung/jcommander/usagebilling/model/CurrentChargesResponse.java
@@ -0,0 +1,56 @@
+package com.baeldung.jcommander.usagebilling.model;
+
+import lombok.*;
+
+import java.math.BigDecimal;
+import java.math.RoundingMode;
+import java.util.List;
+
+@NoArgsConstructor(access = AccessLevel.PACKAGE)
+@AllArgsConstructor(access = AccessLevel.PACKAGE)
+@Builder
+@Getter
+public class CurrentChargesResponse {
+
+ private String customerId;
+ private BigDecimal amountDue;
+ private List lineItems;
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ sb
+ .append("Current Month Charges: {")
+ .append("\n\tcustomer: ")
+ .append(this.customerId)
+ .append("\n\ttotalAmountDue: ")
+ .append(this.amountDue.setScale(2, RoundingMode.HALF_UP))
+ .append("\n\tlineItems: [");
+
+ for (LineItem li : this.lineItems) {
+ sb
+ .append("\n\t\t{")
+ .append("\n\t\t\tsubscription: ")
+ .append(li.subscriptionId)
+ .append("\n\t\t\tamount: ")
+ .append(li.amount.setScale(2, RoundingMode.HALF_UP))
+ .append("\n\t\t\tquantity: ")
+ .append(li.quantity)
+ .append("\n\t\t},");
+ }
+
+ sb.append("\n\t]\n}\n");
+ return sb.toString();
+ }
+
+ @NoArgsConstructor(access = AccessLevel.PACKAGE)
+ @AllArgsConstructor(access = AccessLevel.PACKAGE)
+ @Builder
+ @Getter
+ public static class LineItem {
+
+ private String subscriptionId;
+ private BigDecimal amount;
+ private Integer quantity;
+ }
+}
diff --git a/libraries-3/src/main/java/com/baeldung/jcommander/usagebilling/model/UsageRequest.java b/libraries-3/src/main/java/com/baeldung/jcommander/usagebilling/model/UsageRequest.java
new file mode 100644
index 0000000000..2785474acf
--- /dev/null
+++ b/libraries-3/src/main/java/com/baeldung/jcommander/usagebilling/model/UsageRequest.java
@@ -0,0 +1,50 @@
+package com.baeldung.jcommander.usagebilling.model;
+
+import lombok.*;
+
+import java.math.BigDecimal;
+import java.time.Instant;
+
+@NoArgsConstructor(access = AccessLevel.PACKAGE)
+@AllArgsConstructor(access = AccessLevel.PACKAGE)
+@Builder
+@Getter
+public class UsageRequest {
+
+ private String customerId;
+ private String subscriptionId;
+ private PricingType pricingType;
+ private Integer quantity;
+ private BigDecimal price;
+ private Instant timestamp;
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ sb
+ .append("\nUsage: {")
+ .append("\n\tcustomer: ")
+ .append(this.customerId)
+ .append("\n\tsubscription: ")
+ .append(this.subscriptionId)
+ .append("\n\tquantity: ")
+ .append(this.quantity)
+ .append("\n\ttimestamp: ")
+ .append(this.timestamp)
+ .append("\n\tpricingType: ")
+ .append(this.pricingType);
+
+ if (PricingType.PRE_RATED == this.pricingType) {
+ sb
+ .append("\n\tpreRatedAt: ")
+ .append(this.price);
+ }
+
+ sb.append("\n}\n");
+ return sb.toString();
+ }
+
+ public enum PricingType {
+ PRE_RATED, UNRATED
+ }
+}
diff --git a/libraries-3/src/main/java/com/baeldung/jcommander/usagebilling/service/DefaultFetchCurrentChargesService.java b/libraries-3/src/main/java/com/baeldung/jcommander/usagebilling/service/DefaultFetchCurrentChargesService.java
new file mode 100644
index 0000000000..6436d49875
--- /dev/null
+++ b/libraries-3/src/main/java/com/baeldung/jcommander/usagebilling/service/DefaultFetchCurrentChargesService.java
@@ -0,0 +1,68 @@
+package com.baeldung.jcommander.usagebilling.service;
+
+import com.baeldung.jcommander.usagebilling.model.CurrentChargesRequest;
+import com.baeldung.jcommander.usagebilling.model.CurrentChargesResponse;
+import com.baeldung.jcommander.usagebilling.model.CurrentChargesResponse.LineItem;
+
+import java.math.BigDecimal;
+import java.util.List;
+import java.util.UUID;
+
+import static java.lang.String.format;
+import static java.util.Arrays.asList;
+import static java.util.Arrays.fill;
+import static java.util.Collections.emptyList;
+import static java.util.concurrent.ThreadLocalRandom.current;
+import static java.util.stream.Collectors.toList;
+
+class DefaultFetchCurrentChargesService implements FetchCurrentChargesService {
+
+ @Override
+ public CurrentChargesResponse fetch(CurrentChargesRequest request) {
+ List subscriptions = request.getSubscriptionIds();
+
+ if (subscriptions == null || subscriptions.isEmpty()) {
+ System.out.println("Fetching ALL charges for customer: " + request.getCustomerId());
+ subscriptions = mockSubscriptions();
+
+ } else {
+ System.out.println(format("Fetching charges for customer: %s and subscriptions: %s", request.getCustomerId(), subscriptions));
+ }
+
+ CurrentChargesResponse charges = mockCharges(request.getCustomerId(), subscriptions, request.isItemized());
+ System.out.println("Fetched charges...");
+ return charges;
+ }
+
+ private CurrentChargesResponse mockCharges(String customerId, List subscriptions, boolean itemized) {
+ List lineItems = mockLineItems(subscriptions);
+ BigDecimal amountDue = lineItems
+ .stream()
+ .map(li -> li.getAmount())
+ .reduce(new BigDecimal("0"), BigDecimal::add);
+
+ return CurrentChargesResponse
+ .builder()
+ .customerId(customerId)
+ .lineItems(itemized ? lineItems : emptyList())
+ .amountDue(amountDue)
+ .build();
+ }
+
+ private List mockLineItems(List subscriptions) {
+ return subscriptions
+ .stream()
+ .map(subscription -> LineItem.builder()
+ .subscriptionId(subscription)
+ .quantity(current().nextInt(20))
+ .amount(new BigDecimal(current().nextDouble(1_000)))
+ .build())
+ .collect(toList());
+ }
+
+ private List mockSubscriptions() {
+ String[] subscriptions = new String[5];
+ fill(subscriptions, UUID.randomUUID().toString());
+ return asList(subscriptions);
+ }
+}
diff --git a/libraries-3/src/main/java/com/baeldung/jcommander/usagebilling/service/DefaultSubmitUsageService.java b/libraries-3/src/main/java/com/baeldung/jcommander/usagebilling/service/DefaultSubmitUsageService.java
new file mode 100644
index 0000000000..44ac9e9ed7
--- /dev/null
+++ b/libraries-3/src/main/java/com/baeldung/jcommander/usagebilling/service/DefaultSubmitUsageService.java
@@ -0,0 +1,16 @@
+package com.baeldung.jcommander.usagebilling.service;
+
+import com.baeldung.jcommander.usagebilling.model.UsageRequest;
+
+import java.util.UUID;
+
+class DefaultSubmitUsageService implements SubmitUsageService {
+
+ @Override
+ public String submit(UsageRequest request) {
+ System.out.println("Submitting usage..." + request);
+
+ System.out.println("Submitted usage successfully...");
+ return UUID.randomUUID().toString();
+ }
+}
diff --git a/libraries-3/src/main/java/com/baeldung/jcommander/usagebilling/service/FetchCurrentChargesService.java b/libraries-3/src/main/java/com/baeldung/jcommander/usagebilling/service/FetchCurrentChargesService.java
new file mode 100644
index 0000000000..2cc56658ff
--- /dev/null
+++ b/libraries-3/src/main/java/com/baeldung/jcommander/usagebilling/service/FetchCurrentChargesService.java
@@ -0,0 +1,13 @@
+package com.baeldung.jcommander.usagebilling.service;
+
+import com.baeldung.jcommander.usagebilling.model.CurrentChargesRequest;
+import com.baeldung.jcommander.usagebilling.model.CurrentChargesResponse;
+
+public interface FetchCurrentChargesService {
+
+ static FetchCurrentChargesService getDefault() {
+ return new DefaultFetchCurrentChargesService();
+ }
+
+ CurrentChargesResponse fetch(CurrentChargesRequest request);
+}
diff --git a/libraries-3/src/main/java/com/baeldung/jcommander/usagebilling/service/SubmitUsageService.java b/libraries-3/src/main/java/com/baeldung/jcommander/usagebilling/service/SubmitUsageService.java
new file mode 100644
index 0000000000..2a29e6e474
--- /dev/null
+++ b/libraries-3/src/main/java/com/baeldung/jcommander/usagebilling/service/SubmitUsageService.java
@@ -0,0 +1,12 @@
+package com.baeldung.jcommander.usagebilling.service;
+
+import com.baeldung.jcommander.usagebilling.model.UsageRequest;
+
+public interface SubmitUsageService {
+
+ static SubmitUsageService getDefault() {
+ return new DefaultSubmitUsageService();
+ }
+
+ String submit(UsageRequest request);
+}
diff --git a/libraries-3/src/test/java/com/baeldung/jcommander/helloworld/HelloWorldAppUnitTest.java b/libraries-3/src/test/java/com/baeldung/jcommander/helloworld/HelloWorldAppUnitTest.java
new file mode 100644
index 0000000000..48b3ac2896
--- /dev/null
+++ b/libraries-3/src/test/java/com/baeldung/jcommander/helloworld/HelloWorldAppUnitTest.java
@@ -0,0 +1,28 @@
+package com.baeldung.jcommander.helloworld;
+
+import com.beust.jcommander.JCommander;
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+
+public class HelloWorldAppUnitTest {
+
+ @Test
+ public void whenJCommanderInvokedWithArgs_thenArgsParsed() {
+
+ HelloWorldArgs jArgs = new HelloWorldArgs();
+ JCommander helloCmd = JCommander
+ .newBuilder()
+ .addObject(jArgs)
+ .build();
+
+ // when
+ String[] argv = new String[] {
+ "--name", "JavaWorld"
+ };
+ helloCmd.parse(argv);
+
+ // then
+ assertEquals("JavaWorld", jArgs.getName());
+ }
+}
diff --git a/libraries-3/src/test/java/com/baeldung/jcommander/usagebilling/cli/FetchCurrentChargesCommandUnitTest.java b/libraries-3/src/test/java/com/baeldung/jcommander/usagebilling/cli/FetchCurrentChargesCommandUnitTest.java
new file mode 100644
index 0000000000..b639661c39
--- /dev/null
+++ b/libraries-3/src/test/java/com/baeldung/jcommander/usagebilling/cli/FetchCurrentChargesCommandUnitTest.java
@@ -0,0 +1,61 @@
+package com.baeldung.jcommander.usagebilling.cli;
+
+import com.beust.jcommander.JCommander;
+import org.junit.Test;
+
+import static org.hamcrest.collection.IsIterableContainingInOrder.contains;
+import static org.junit.Assert.assertThat;
+
+public class FetchCurrentChargesCommandUnitTest {
+
+ private JCommander jc = JCommander.newBuilder()
+ .addObject(new FetchCurrentChargesCommand())
+ .build();
+
+ @Test
+ public void whenParsedMultipleSubscriptionsParameter_thenParameterSubscriptionsIsPopulated() {
+ FetchCurrentChargesCommand cmd = (FetchCurrentChargesCommand) jc
+ .getObjects()
+ .get(0);
+
+ jc.parse(new String[] {
+ "-C", "cb898e7a-f2a0-46d2-9a09-531f1cee1839",
+ "-S", "subscriptionA001",
+ "-S", "subscriptionA002",
+ "-S", "subscriptionA003",
+ });
+
+ assertThat(cmd.getSubscriptionIds(),
+ contains("subscriptionA001", "subscriptionA002", "subscriptionA003"));
+ }
+
+ @Test
+ public void whenParsedSubscriptionsColonSeparatedParameter_thenParameterSubscriptionsIsPopulated() {
+ FetchCurrentChargesCommand cmd = (FetchCurrentChargesCommand) jc
+ .getObjects()
+ .get(0);
+
+ jc.parse(new String[] {
+ "-C", "cb898e7a-f2a0-46d2-9a09-531f1cee1839",
+ "-S", "subscriptionA001:subscriptionA002:subscriptionA003",
+ });
+
+ assertThat(cmd.getSubscriptionIds(),
+ contains("subscriptionA001", "subscriptionA002", "subscriptionA003"));
+ }
+
+ @Test
+ public void whenParsedSubscriptionsWithVariableArity_thenParameterSubscriptionsIsPopulated() {
+ FetchCurrentChargesCommand cmd = (FetchCurrentChargesCommand) jc
+ .getObjects()
+ .get(0);
+
+ jc.parse(new String[] {
+ "-C", "cb898e7a-f2a0-46d2-9a09-531f1cee1839",
+ "-S", "subscriptionA001", "subscriptionA002", "subscriptionA003",
+ });
+
+ assertThat(cmd.getSubscriptionIds(),
+ contains("subscriptionA001", "subscriptionA002", "subscriptionA003"));
+ }
+}
diff --git a/libraries-3/src/test/java/com/baeldung/jcommander/usagebilling/cli/SubmitUsageCommandUnitTest.java b/libraries-3/src/test/java/com/baeldung/jcommander/usagebilling/cli/SubmitUsageCommandUnitTest.java
new file mode 100644
index 0000000000..d6ce132053
--- /dev/null
+++ b/libraries-3/src/test/java/com/baeldung/jcommander/usagebilling/cli/SubmitUsageCommandUnitTest.java
@@ -0,0 +1,64 @@
+package com.baeldung.jcommander.usagebilling.cli;
+
+import com.beust.jcommander.JCommander;
+import com.beust.jcommander.ParameterException;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+
+public class SubmitUsageCommandUnitTest {
+
+ private JCommander jc = JCommander.newBuilder()
+ .addObject(new SubmitUsageCommand())
+ .build();
+
+ @Test
+ public void whenParsedCustomerParameter_thenParameterOfTypeStringIsPopulated() {
+ jc.parse(new String[] {
+ "--customer", "cb898e7a-f2a0-46d2-9a09-531f1cee1839",
+ "--subscription", "subscriptionPQRMN001",
+ "--pricing-type", "PRE_RATED",
+ "--timestamp", "2019-10-03T10:58:00",
+ "--quantity", "7",
+ "--price", "24.56"
+ });
+
+ SubmitUsageCommand cmd = (SubmitUsageCommand) jc
+ .getObjects()
+ .get(0);
+ assertEquals("cb898e7a-f2a0-46d2-9a09-531f1cee1839", cmd.getCustomerId());
+ }
+
+ @Test
+ public void whenParsedTimestampParameter_thenParameterOfTypeInstantIsPopulated() {
+ jc.parse(new String[] {
+ "--customer", "cb898e7a-f2a0-46d2-9a09-531f1cee1839",
+ "--subscription", "subscriptionPQRMN001",
+ "--pricing-type", "PRE_RATED",
+ "--timestamp", "2019-10-03T10:58:00",
+ "--quantity", "7",
+ "--price", "24.56"
+ });
+
+ SubmitUsageCommand cmd = (SubmitUsageCommand) jc
+ .getObjects()
+ .get(0);
+ assertEquals("2019-10-03T10:58:00Z", cmd
+ .getTimestamp()
+ .toString());
+ }
+
+ @Test(expected = ParameterException.class)
+ public void whenParsedCustomerIdNotUUID_thenParameterException() {
+ jc.parse(new String[] {
+ "--customer", "customer001",
+ "--subscription", "subscriptionPQRMN001",
+ "--pricing-type", "PRE_RATED",
+ "--timestamp", "2019-10-03T10:58:00",
+ "--quantity", "7",
+ "--price", "24.56"
+ });
+ }
+}
diff --git a/pom.xml b/pom.xml
index d2433d11b3..d692e507f9 100644
--- a/pom.xml
+++ b/pom.xml
@@ -541,6 +541,7 @@
libraries
libraries-2
+ libraries-3
libraries-data
libraries-data-2
libraries-data-db
@@ -1307,6 +1308,7 @@
libraries
+ libraries-3
libraries-data
libraries-data-2
libraries-data-db