Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 35 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -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
28 changes: 26 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 файл.**
43 changes: 43 additions & 0 deletions dependency-reduced-pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
<?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/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.example</groupId>
<artifactId>solution</artifactId>
<version>1.0-SNAPSHOT</version>
<build>
<plugins>
<plugin>
<artifactId>maven-shade-plugin</artifactId>
<version>3.5.1</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<transformers>
<transformer>
<mainClass>application.Main</mainClass>
</transformer>
</transformers>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
<dependencies>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.30</version>
<scope>provided</scope>
</dependency>
</dependencies>
<properties>
<maven.compiler.target>19</maven.compiler.target>
<maven.compiler.source>19</maven.compiler.source>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
</project>
38 changes: 0 additions & 38 deletions format.json

This file was deleted.

20 changes: 20 additions & 0 deletions input.json
Original file line number Diff line number Diff line change
@@ -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"
}
]
71 changes: 71 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
<?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>org.example</groupId>
<artifactId>solution</artifactId>
<version>1.0-SNAPSHOT</version>

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

<dependencies>
<!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-core -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>2.16.1</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-annotations -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
<version>2.16.1</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-databind -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.16.1</version>
</dependency>

<!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.30</version>
<scope>provided</scope>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.5.1</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<transformers>
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<mainClass>application.Main</mainClass>
</transformer>
</transformers>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>

</project>
20 changes: 20 additions & 0 deletions src/main/java/application/Main.java
Original file line number Diff line number Diff line change
@@ -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<Order> orders = objectMapper.readValue(new File("input.json"), new TypeReference<>(){});
System.out.println(ReportGenerator.generate(orders));
} catch (IOException e) {
System.out.println(e.getMessage());
}
}
}
14 changes: 14 additions & 0 deletions src/main/java/application/Report.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package application;

import com.fasterxml.jackson.annotation.JsonProperty;

import java.util.List;

public class Report {
@JsonProperty("months")
private List<String> months;

public Report(List<String> months) {
this.months = months;
}
}
52 changes: 52 additions & 0 deletions src/main/java/application/ReportGenerator.java
Original file line number Diff line number Diff line change
@@ -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<Order> orders) throws IOException {
Map<Integer, Double> earningsForEachMonth = countEarningsForEachMonth(orders); // Формируем мапу из списка всех заказов, ключом в которой
// является порядковый номер месяца, а значением - сумма потраченная в этот месяц
List<String> maxMonths = findMonthsWithMaxEarnings(earningsForEachMonth); // Формируем список с месяцами, в которые совершались наибольшие затраты

ObjectMapper objectMapper = new ObjectMapper();
StringWriter output = new StringWriter();
objectMapper.writeValue(output, new Report(maxMonths));
return output.toString();
}

private static Map<Integer, Double> countEarningsForEachMonth(List<Order> orders) {
Map<Integer, Double> 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<String> findMonthsWithMaxEarnings(Map<Integer, Double> earningsForEachMonth) {
List<String> maxMonths = new ArrayList<>();
if (earningsForEachMonth.isEmpty()) {return maxMonths;} // если не проверить, то при вызове Collections.max() может вылететь ошибка
double maxValue = Collections.max(earningsForEachMonth.values());
for (Map.Entry<Integer, Double> entry : earningsForEachMonth.entrySet()) {
if (entry.getValue().equals(maxValue)) {
maxMonths.add(Month.of(entry.getKey()).toString().toLowerCase());
}
}
return maxMonths;
}
}
18 changes: 18 additions & 0 deletions src/main/java/data/Order.java
Original file line number Diff line number Diff line change
@@ -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;
}
8 changes: 8 additions & 0 deletions src/main/java/data/Status.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package data;

public enum Status {
COMPLETED,
CANCELED,
CREATED,
DELIVERY
}
27 changes: 27 additions & 0 deletions src/main/java/utils/LocalDateTimeDeserializer.java
Original file line number Diff line number Diff line change
@@ -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<LocalDateTime> {
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);
}
}
3 changes: 3 additions & 0 deletions src/main/resources/META-INF/MANIFEST.MF
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Manifest-Version: 1.0
Main-Class: application.Main