diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..65f20b1 --- /dev/null +++ b/.gitignore @@ -0,0 +1,35 @@ +target/ +!.mvn/wrapper/maven-wrapper.jar +!**/src/main/**/target/ +!**/src/test/**/target/ + +### IntelliJ IDEA ### +.idea/ +*.iws +*.iml +*.ipr + +### Eclipse ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +build/ +!**/src/main/**/build/ +!**/src/test/**/build/ + +### VS Code ### +.vscode/ + +### Mac OS ### +.DS_Store diff --git a/README.md b/README.md index ee9d69b..448015e 100644 --- a/README.md +++ b/README.md @@ -30,7 +30,31 @@ 4. Найденный в соответствии с условием задачи месяц должен выводиться на английском языке в нижнем регистре. Если месяцев несколько, то на вывод они все подаются на английском языке в нижнем регистре в порядке их следования в течение года. ## Автор решения - +Гуренков Максим Сергеевич ## Описание реализации - +Для выполнения задачи было решено использовать библиотеки jackson и lombok. +### Пакет "application" +В пакете `application` находятся классы, реализующие основную логику работы программы: `Main`, `Report` и `ReportGenerator`. + - В классе `Main`, с помощью библиотеки jackson данными из файла `input.json` инициализируется список объектов типа `Order`, который потом передается на вход статическому методу `generate()` класса `ReportGenerator`. Возвращаемая json-строка выводится на экран. + - Класс `Report` содержит в себе только одно поле - список месяцев, в которые были произведены наибольшие затраты. Используется для сериализации результата в json формат. + - Класс `ReportGenerator` отвечает за генерацию отчетов. Содержит один публичный (`generate()`) и два приватных метода (`countEarningsForEachMonth()` и `findMonthsWithMaxEarnings()`). + + В методе `countEarningsForEachMonth()` создается `Map`, где ключ - порядковый номер месяца (`Integer`), а значение - общая сумма трат в этом месяце (`Double`). В качестве реализации была выбрана коллекция `TreeMap`, так как она поддерживает автоматическую сортировку элементов по ключу в естественном порядке. Далее в цикле выполняется обход массива заказов, и заполняется `Map`. + + Метод `findMonthsWithMaxEarnings()` отвечает за формирование списка с месяцами, в которые были произведены максимальные затраты. + + Метод `generate()` использует все перечисленные методы для формирования отчета типа `Report`, после чего с помощью библиотеки jackson сериализуется в json формат. +### Пакет "data" +В пакете data находятся классы, представляющие из себя объектную модель входных данных: `Order` и `Status`. + - Класс `Order` содержит все поля, соответствующие аттрибутам элементов входного массива: `userId`, `orderedAt`, `status`, `total`. + - Перечисление `Status` - тип данных для поля `status` класса `Order`: COMPLETED, CANCELED, CREATED, DELIVERY. +### Пакет "utils" +В пакете `utils` содержится один класс - `LocalDateTimeDeserializer`, необходимый для корректной десериализации даты в поле типа `LocalDateTime` с помощью jackson. ## Инструкция по сборке и запуску решения +- Убедитесь, что у вас установлен maven +- Перейдите в корневую директорию проекта и выполните команду: +```bash +mvn package +``` +- В сгенерированной директории `target` будет лежать jar файл `solution-1.0-SNAPSHOT.jar`, который можно запустить с помощью команды: +```bash +java -jar solution-1.0-SNAPSHOT.jar +``` +**Обратите внимание, что для корректной работы программы входные данные должны находиться в файле с названием `input.json`, который должен лежать в той же директории, что и jar файл.** \ No newline at end of file diff --git a/dependency-reduced-pom.xml b/dependency-reduced-pom.xml new file mode 100644 index 0000000..c2d3fe6 --- /dev/null +++ b/dependency-reduced-pom.xml @@ -0,0 +1,43 @@ + + + 4.0.0 + org.example + solution + 1.0-SNAPSHOT + + + + maven-shade-plugin + 3.5.1 + + + package + + shade + + + + + application.Main + + + + + + + + + + + org.projectlombok + lombok + 1.18.30 + provided + + + + 19 + 19 + UTF-8 + + diff --git a/format.json b/format.json deleted file mode 100644 index 94aad31..0000000 --- a/format.json +++ /dev/null @@ -1,38 +0,0 @@ -[ - { - "user_id": "3acfb0b7-04bd-4978-be4c-3929372277c1", - "ordered_at": "2023-01-16T13:56:39.492", - "status": "COMPLETED", - "total": "1917.00" - }, - { - "user_id": "25b003b9-ab22-4a24-a616-dd0303f983d8", - "ordered_at": "2023-03-05T08:34:21.123", - "status": "COMPLETED", - "total": "13990.00" - }, - { - "user_id": "e1470ada-fcbb-4424-8c46-065b6409ca4b", - "ordered_at": "2016-03-16T13:56:39.492", - "status": "COMPLETED", - "total": "215.50" - }, - { - "user_id": "081a47a5-b7bf-462c-a11a-68002a179152", - "ordered_at": "2023-12-08T21:36:59.281", - "status": "COMPLETED", - "total": "49499.00" - }, - { - "user_id": "0999c6aa-1bac-4ded-9a54-92fff4f34d69", - "ordered_at": "2023-12-14T11:10:29.408", - "status": "CANCELED", - "total": "13650.00" - }, - { - "user_id": "0999c6aa-1bac-4ded-9a54-92fff4f34d69", - "ordered_at": "2023-12-14T11:15:31.108", - "status": "COMPLETED", - "total": "14760.00" - } -] \ No newline at end of file diff --git a/input.json b/input.json new file mode 100644 index 0000000..d50f878 --- /dev/null +++ b/input.json @@ -0,0 +1,20 @@ +[ + { + "user_id": "0999c6aa-1bac-4ded-9a54-92fff4f34d69", + "ordered_at": "2023-12-14T11:15:31.108", + "status": "COMPLETED", + "total": "500" + }, + { + "user_id": "0999c6aa-1bac-4ded-9a54-92fff4f34d69", + "ordered_at": "2023-12-14T11:15:31.108", + "status": "CANCELED", + "total": "5000" + }, + { + "user_id": "0999c6aa-1bac-4ded-9a54-92fff4f34d69", + "ordered_at": "2023-01-14T11:15:31.108", + "status": "COMPLETED", + "total": "500" + } +] diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..cb48630 --- /dev/null +++ b/pom.xml @@ -0,0 +1,71 @@ + + + 4.0.0 + + org.example + solution + 1.0-SNAPSHOT + + + 19 + 19 + UTF-8 + + + + + + com.fasterxml.jackson.core + jackson-core + 2.16.1 + + + + com.fasterxml.jackson.core + jackson-annotations + 2.16.1 + + + + com.fasterxml.jackson.core + jackson-databind + 2.16.1 + + + + + org.projectlombok + lombok + 1.18.30 + provided + + + + + + + org.apache.maven.plugins + maven-shade-plugin + 3.5.1 + + + package + + shade + + + + + application.Main + + + + + + + + + + diff --git a/src/main/java/application/Main.java b/src/main/java/application/Main.java new file mode 100644 index 0000000..a5aa446 --- /dev/null +++ b/src/main/java/application/Main.java @@ -0,0 +1,20 @@ +package application; + +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; +import data.Order; +import java.io.File; +import java.io.IOException; +import java.util.List; + +class Main { + public static void main(String[] args) { + ObjectMapper objectMapper = new ObjectMapper(); + try { + List orders = objectMapper.readValue(new File("input.json"), new TypeReference<>(){}); + System.out.println(ReportGenerator.generate(orders)); + } catch (IOException e) { + System.out.println(e.getMessage()); + } + } +} diff --git a/src/main/java/application/Report.java b/src/main/java/application/Report.java new file mode 100644 index 0000000..14c0fd4 --- /dev/null +++ b/src/main/java/application/Report.java @@ -0,0 +1,14 @@ +package application; + +import com.fasterxml.jackson.annotation.JsonProperty; + +import java.util.List; + +public class Report { + @JsonProperty("months") + private List months; + + public Report(List months) { + this.months = months; + } +} diff --git a/src/main/java/application/ReportGenerator.java b/src/main/java/application/ReportGenerator.java new file mode 100644 index 0000000..61797cf --- /dev/null +++ b/src/main/java/application/ReportGenerator.java @@ -0,0 +1,52 @@ +package application; + +import com.fasterxml.jackson.databind.ObjectMapper; +import data.Order; +import data.Status; +import java.io.IOException; +import java.io.StringWriter; +import java.time.Month; +import java.util.*; + +public class ReportGenerator { + public static String generate(List orders) throws IOException { + Map earningsForEachMonth = countEarningsForEachMonth(orders); // Формируем мапу из списка всех заказов, ключом в которой + // является порядковый номер месяца, а значением - сумма потраченная в этот месяц + List maxMonths = findMonthsWithMaxEarnings(earningsForEachMonth); // Формируем список с месяцами, в которые совершались наибольшие затраты + + ObjectMapper objectMapper = new ObjectMapper(); + StringWriter output = new StringWriter(); + objectMapper.writeValue(output, new Report(maxMonths)); + return output.toString(); + } + + private static Map countEarningsForEachMonth(List orders) { + Map earningsForEachMonth = new TreeMap<>(); // Создаем TreeMap, так как тогда коллекция будет по умолчанию + // отсортированна по ключу в порядке возрастания (в нашем случае это означает, что месяца будут располагаться в порядке их следования в течение года) + for (int i = 0; i < orders.size(); i ++) { // проходимся по списку заказов + if (orders.get(i).getStatus() == Status.COMPLETED) { + int month = orders.get(i).getOrderedAt().getMonthValue(); + if (earningsForEachMonth.get(month) == null) { + earningsForEachMonth.put(month, orders.get(i).getTotal()); // если в мапе еще нет ни одного + // элемента с таким ключом, то кладем его туда, в качестве значения указываем сумму заказа + } else { + earningsForEachMonth.put(month, earningsForEachMonth.get(month) + orders.get(i).getTotal()); // если в мапе уже есть элемент с таким ключом, + // то прибавляем к значению в мапе сумму текущего заказа + } + } + } + return earningsForEachMonth; + } + + private static List findMonthsWithMaxEarnings(Map earningsForEachMonth) { + List maxMonths = new ArrayList<>(); + if (earningsForEachMonth.isEmpty()) {return maxMonths;} // если не проверить, то при вызове Collections.max() может вылететь ошибка + double maxValue = Collections.max(earningsForEachMonth.values()); + for (Map.Entry entry : earningsForEachMonth.entrySet()) { + if (entry.getValue().equals(maxValue)) { + maxMonths.add(Month.of(entry.getKey()).toString().toLowerCase()); + } + } + return maxMonths; + } +} \ No newline at end of file diff --git a/src/main/java/data/Order.java b/src/main/java/data/Order.java new file mode 100644 index 0000000..9ccee84 --- /dev/null +++ b/src/main/java/data/Order.java @@ -0,0 +1,18 @@ +package data; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import lombok.Data; +import utils.LocalDateTimeDeserializer; +import java.time.LocalDateTime; + +@Data +public class Order { + @JsonProperty("user_id") + private String userId; + @JsonProperty("ordered_at") + @JsonDeserialize(using = LocalDateTimeDeserializer.class) + private LocalDateTime orderedAt; + private Status status; + private double total; +} diff --git a/src/main/java/data/Status.java b/src/main/java/data/Status.java new file mode 100644 index 0000000..f889418 --- /dev/null +++ b/src/main/java/data/Status.java @@ -0,0 +1,8 @@ +package data; + +public enum Status { + COMPLETED, + CANCELED, + CREATED, + DELIVERY +} diff --git a/src/main/java/utils/LocalDateTimeDeserializer.java b/src/main/java/utils/LocalDateTimeDeserializer.java new file mode 100644 index 0000000..e3107c3 --- /dev/null +++ b/src/main/java/utils/LocalDateTimeDeserializer.java @@ -0,0 +1,27 @@ +package utils; + +import java.io.IOException; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.deser.std.StdDeserializer; + +public class LocalDateTimeDeserializer extends StdDeserializer { + private static final DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSS"); + public LocalDateTimeDeserializer() { + this(null); + } + + public LocalDateTimeDeserializer(Class vc) { + super(vc); + } + + @Override + public LocalDateTime deserialize(JsonParser jsonparser, DeserializationContext context) throws IOException { + JsonNode node = jsonparser.getCodec().readTree(jsonparser); + String strDate = node.asText(); + return LocalDateTime.parse(strDate, formatter); + } +} diff --git a/src/main/resources/META-INF/MANIFEST.MF b/src/main/resources/META-INF/MANIFEST.MF new file mode 100644 index 0000000..c539e19 --- /dev/null +++ b/src/main/resources/META-INF/MANIFEST.MF @@ -0,0 +1,3 @@ +Manifest-Version: 1.0 +Main-Class: application.Main +