Skip to content

Commit 3baa972

Browse files
committed
Add Homework 02 assignment
1 parent 94750f1 commit 3baa972

4 files changed

Lines changed: 5098 additions & 3 deletions

File tree

Lines changed: 352 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,352 @@
1+
# Домашно 2
2+
3+
## Space Scanner :rocket:
4+
5+
`Краен срок: 22.12.2025, 23:59`
6+
7+
### Условие
8+
9+
Ще създадем приложение за извличане на статистически резултати за мисиите в Космоса от 1957г. насам, заедно с ракетите,
10+
използвани за съответните мисии.
11+
12+
Ще използваме data set от [kaggle](https://www.kaggle.com). Данните за мисиите са налични в CSV файлa [all-missions-from-1957.csv](./resources/all-missions-from-1957.csv). Данните за ракетите са налични в CSV файла [all-rockets-from-1957.csv](./resources/all-rockets-from-1957.csv).
13+
14+
Имайте предвид, че е възможно в real-life data set да има непълни записи, т.е. да липсва информация за дадена колона.
15+
Такива ще моделираме с `Optional`. Обърнете внимание, че символът за запетая участва както като разделител между колоните, така и като част от данните в самите тях.
16+
17+
### Задължителни интерфейси и класове
18+
19+
В пакета `bg.sofia.uni.fmi.mjt.space` създайте клас `MJTSpaceScanner`, който има конструктор, приемащ мисиите, под формата на `Reader`, ракетите, отново под формата на `Reader`, както и частен ключ, който се използва за криптиране и декриптиране на конфиденциална информация, използвайки **Rijndael** (или **AES**) алгоритъма.
20+
21+
```java
22+
public MJTSpaceScanner(Reader missionsReader, Reader rocketsReader, SecretKey secretKey)
23+
```
24+
25+
Класът `MJTSpaceScanner` имплементира интерфейса `SpaceScannerAPI`:
26+
27+
```java
28+
package bg.sofia.uni.fmi.mjt.space;
29+
30+
import bg.sofia.uni.fmi.mjt.space.mission.Mission;
31+
import bg.sofia.uni.fmi.mjt.space.mission.MissionStatus;
32+
import bg.sofia.uni.fmi.mjt.space.rocket.Rocket;
33+
import bg.sofia.uni.fmi.mjt.space.rocket.RocketStatus;
34+
35+
import java.io.OutputStream;
36+
import java.time.LocalDate;
37+
import java.util.Collection;
38+
import java.util.List;
39+
import java.util.Map;
40+
import java.util.Optional;
41+
42+
public interface SpaceScannerAPI {
43+
/**
44+
* Returns all missions in the dataset.
45+
* If there are no missions, return an empty collection.
46+
*/
47+
Collection<Mission> getAllMissions();
48+
49+
/**
50+
* Returns all missions in the dataset with a given status.
51+
* If there are no missions, return an empty collection.
52+
*
53+
* @param missionStatus the status of the missions
54+
* @throws IllegalArgumentException if missionStatus is null
55+
*/
56+
Collection<Mission> getAllMissions(MissionStatus missionStatus);
57+
58+
/**
59+
* Returns the company with the most successful missions in a given time period.
60+
* Success is defined as MissionStatus.SUCCESS.
61+
* If multiple companies have the same number of successful missions, return any of them.
62+
* If there are no successful missions in the period, return an empty string.
63+
* If there are no missions at all, return an empty string.
64+
*
65+
* @param from the inclusive beginning of the time frame
66+
* @param to the inclusive end of the time frame
67+
* @throws IllegalArgumentException if from or to is null
68+
* @throws TimeFrameMismatchException if to is before from
69+
*/
70+
String getCompanyWithMostSuccessfulMissions(LocalDate from, LocalDate to);
71+
72+
/**
73+
* Groups missions by country.
74+
* If there are no missions, return an empty map.
75+
*/
76+
Map<String, Collection<Mission>> getMissionsPerCountry();
77+
78+
/**
79+
* Returns the top N least expensive missions, ordered from cheapest to more expensive.
80+
* If there are no missions, return an empty list.
81+
*
82+
* @param n the number of missions to be returned
83+
* @param missionStatus the status of the missions
84+
* @param rocketStatus the status of the rockets
85+
* @throws IllegalArgumentException if n is less than or equal to 0, missionStatus or rocketStatus is null
86+
*/
87+
List<Mission> getTopNLeastExpensiveMissions(int n, MissionStatus missionStatus, RocketStatus rocketStatus);
88+
89+
/**
90+
* Returns the most desired location for each company.
91+
* Most desired = location with the highest number of missions for that company.
92+
* Location is defined as the value in the "Location" column (e.g., "Kennedy Space Center, FL, USA").
93+
* If a company has multiple locations with the same count, return any of them.
94+
* If there are no missions, return an empty map.
95+
*
96+
* @return a map where keys are company names and values are their most used mission locations
97+
*/
98+
Map<String, String> getMostDesiredLocationForMissionsPerCompany();
99+
100+
/**
101+
* Returns the location with most successful missions for each company in a given time period.
102+
* Successful = MissionStatus.SUCCESS.
103+
* For each company, finds the location where that company had the most successful missions.
104+
* If a company has multiple locations with the same count of successful missions, return any of them.
105+
* If a company has no successful missions in the period, it is NOT included in the result.
106+
* If there are no missions at all, return an empty map.
107+
*
108+
* @param from the inclusive beginning of the time frame (inclusive)
109+
* @param to the inclusive end of the time frame (inclusive)
110+
* @return a map where keys are company names and values are their locations with most successful missions in the period
111+
* @throws IllegalArgumentException if from or to is null
112+
* @throws TimeFrameMismatchException if to is before from
113+
*/
114+
Map<String, String> getLocationWithMostSuccessfulMissionsPerCompany(LocalDate from, LocalDate to);
115+
116+
/**
117+
* Returns all rockets in the dataset.
118+
* If there are no rockets, return an empty collection.
119+
*/
120+
Collection<Rocket> getAllRockets();
121+
122+
/**
123+
* Returns the top N tallest rockets, in decreasing order.
124+
* If there are no rockets, return an empty list.
125+
*
126+
* @param n the number of rockets to be returned
127+
* @throws IllegalArgumentException if n is less than or equal to 0
128+
*/
129+
List<Rocket> getTopNTallestRockets(int n);
130+
131+
/**
132+
* Returns a mapping of rockets (by name) to their respective wiki page (if present).
133+
* If there are no rockets, return an empty map.
134+
*/
135+
Map<String, Optional<String>> getWikiPageForRocket();
136+
137+
/**
138+
* Returns the wiki pages for the rockets used in the N most expensive missions.
139+
* If there are no missions, return an empty list.
140+
*
141+
* @param n the number of missions to be returned
142+
* @param missionStatus the status of the missions
143+
* @param rocketStatus the status of the rockets
144+
* @throws IllegalArgumentException if n is less than or equal to 0, or missionStatus or rocketStatus is null
145+
*/
146+
List<String> getWikiPagesForRocketsUsedInMostExpensiveMissions(int n, MissionStatus missionStatus,
147+
RocketStatus rocketStatus);
148+
149+
/**
150+
* Saves the name of the most reliable rocket in a given time period in an encrypted format.
151+
*
152+
* @param outputStream the output stream where the encrypted result is written into
153+
* @param from the inclusive beginning of the time frame
154+
* @param to the inclusive end of the time frame
155+
* @throws IllegalArgumentException if outputStream, from or to is null
156+
* @throws CipherException if the encrypt/decrypt operation cannot be completed successfully
157+
* @throws TimeFrameMismatchException if to is before from
158+
*/
159+
void saveMostReliableRocket(OutputStream outputStream, LocalDate from, LocalDate to) throws CipherException;
160+
}
161+
```
162+
163+
#### Record `Mission`
164+
165+
Една мисия се моделира от следния `record`:
166+
167+
```java
168+
Mission(String id, String company, String location, LocalDate date, Detail detail, RocketStatus rocketStatus, Optional<Double> cost, MissionStatus missionStatus)
169+
```
170+
171+
В нея, един **Detail** се моделира от следния `record`:
172+
173+
#### Record `Detail`
174+
175+
```java
176+
public record Detail(String rocketName, String payload)
177+
```
178+
179+
който се състои от двa компонента, разделени в data set-a един от друг с "|". Форматът е: `<rocketName>|<payload>`.
180+
181+
Възможните резултати за всяка мисия са един от `Success, Failure, Partial Failure, Prelaunch Failure` и се моделират от следния `enum`:
182+
183+
#### Enum `MissionStatus`
184+
185+
```java
186+
package bg.sofia.uni.fmi.mjt.space.mission;
187+
188+
public enum MissionStatus {
189+
SUCCESS("Success"),
190+
FAILURE("Failure"),
191+
PARTIAL_FAILURE("Partial Failure"),
192+
PRELAUNCH_FAILURE("Prelaunch Failure");
193+
194+
private final String value;
195+
196+
MissionStatus(String value) {
197+
this.value = value;
198+
}
199+
200+
public String toString() {
201+
return value;
202+
}
203+
}
204+
```
205+
206+
#### Record `Rocket`
207+
208+
Една ракета се моделира от следния record:
209+
210+
```java
211+
public record Rocket(String id, String name, Optional<String> wiki, Optional<Double> height)
212+
```
213+
214+
След дадена масия, изполваната ракета може да бъде все още активна (**StatusActive**), или вече да не е в експлоатация (**StatusRetired**). Моделираме го със следния `enum`:
215+
216+
#### Enum `RocketStatus`
217+
218+
```java
219+
package bg.sofia.uni.fmi.mjt.space.rocket;
220+
221+
public enum RocketStatus {
222+
STATUS_RETIRED("StatusRetired"),
223+
STATUS_ACTIVE("StatusActive");
224+
225+
private final String value;
226+
227+
RocketStatus(String value) {
228+
this.value = value;
229+
}
230+
231+
public String toString() {
232+
return value;
233+
}
234+
}
235+
```
236+
237+
Трите record-a: `Mission`, `Detail` и `Rocket` трябва да имат публичен каноничен конструктор.
238+
239+
#### Rocket reliability
240+
241+
Reliability-то на дадена ракета ще пресмятаме по следната формула:
242+
243+
```
244+
(2 * (броя на успешните мисии на ракетата) + (броя на неуспешните мисии на ракетата)) / (2 * (броя на всички мисии на ракетата))
245+
```
246+
247+
Неуспешна мисия считаме за такава със статус `MissionStatus.FAILURE`, `MissionStatus.PARTIAL_FAILURE` или `MissionStatus.PRELAUNCH_FAILURE`.
248+
Ракетите, които не са участвали в мисии, имат reliability 0.0.
249+
250+
> Пример: Ракета с 3 успешни мисии и 1 неуспешна:
251+
> Reliability = (2*3 + 1) / (2*4) = 7/8 = 0.875
252+
253+
Алгоритъмът за криптиране (**AES**) има имплементация в JDK-то (в `javax.crypto` пакета) и за него сме ви дали [code snippet](https://github.com/fmi/java-course/blob/master/07-io-streams-and-files/snippets/src/bg/sofia/uni/fmi/mjt/io/CipherExample.java). Създайте клас **Rijndael**, който има следния конструктор:
254+
255+
```java
256+
/**
257+
* Encrypts/decrypts data using AES (Rijndael) algorithm with the provided secret key.
258+
*
259+
* @param secretKey the encryption/decryption key
260+
* @throws IllegalArgumentException if secretKey is null
261+
*/
262+
public Rijndael(SecretKey secretKey)
263+
```
264+
265+
и имплементира интерфейса:
266+
267+
```java
268+
package bg.sofia.uni.fmi.mjt.space.algorithm;
269+
270+
import java.io.InputStream;
271+
import java.io.OutputStream;
272+
273+
public interface SymmetricBlockCipher {
274+
/**
275+
* Encrypts the data from inputStream and puts it into outputStream
276+
*
277+
* @param inputStream the input stream where the data is read from
278+
* @param outputStream the output stream where the encrypted result is written into
279+
* @throws CipherException if the encrypt/decrypt operation cannot be completed successfully
280+
*/
281+
void encrypt(InputStream inputStream, OutputStream outputStream) throws CipherException;
282+
283+
/**
284+
* Decrypts the data from inputStream and puts it into outputStream
285+
*
286+
* @param inputStream the input stream where the data is read from
287+
* @param outputStream the output stream where the decrypted result is written into
288+
* @throws CipherException if the encrypt/decrypt operation cannot be completed successfully
289+
*/
290+
void decrypt(InputStream inputStream, OutputStream outputStream) throws CipherException;
291+
}
292+
```
293+
294+
### Тестване
295+
296+
Създайте автоматични тестове, с които да тествате решението си.
297+
298+
### Структура на проекта
299+
300+
Спазвайте имената на пакетите на всички по-долу описани класове, тъй като в противен случай решението ви няма да може да бъде тествано от грейдъра.
301+
302+
```bash
303+
src
304+
└── bg.sofia.uni.fmi.mjt.space
305+
├── algorithm
306+
│ ├── Rijndael.java
307+
│ └── SymmetricBlockCipher.java
308+
├── exception
309+
│ ├── CipherException.java
310+
│ └── TimeFrameMismatchException.java
311+
├── mission
312+
│ ├── Detail.java
313+
│ ├── Mission.java
314+
│ └── MissionStatus.java
315+
├── rocket
316+
│ ├── Rocket.java
317+
│ └── RocketStatus.java
318+
├── MJTSpaceScanner.java
319+
├── SpaceScannerAPI.java
320+
└── (...)
321+
test
322+
└── bg.sofia.uni.fmi.mjt.space
323+
└── (...)
324+
```
325+
326+
Обърнете внимание, че при качване на решението ви, в грейдъра ще се изпълни само _smoke_ тест, чиято цел е да изчистите
327+
евентуални проблеми с компилацията. Референтите тестове и Checkstyle статичният код анализ ще се изпълнят еднократно
328+
след изтичане на крайния срок за предаване. За функционалната коректност и качеството на кода ще трябва да се погрижите
329+
без тяхната помощ.
330+
331+
### :warning: Важно!
332+
333+
⚡ 1. Уверете се, че решението ви се компилира **в грейдъра**. Има ясна индикация за това като го качвате. Ако не го докарате до успешна компилация преди крайния срок, решението ви няма да бъде преглеждано и ще бъде оценено с нула точки.
334+
335+
⚡ 2. Уверете се, че решението ви зарежда и работи успешно с **целия оригинален data set**, наличен в `/resources`, а не само с някакъв внимателно подбран от вас subset. Ако при изпълнение на референтните тестове зареждането на data set-a фейлне с решението ви, ще фелйнат и всички тестове, и ще финиширате с нула точки за това домашно.
336+
337+
### Предаване
338+
339+
Качете `src` и `test` директориите на проекта (или `.zip` архив, който ги съдържа) в съответния assignment в грейдъра. Ако ползвате статични файлове за тестване, те трябва да се намират директно на нивото на `src` и `test`, без да са в отделна директория. Препоръчваме обаче да създадете автоматични тестове, които не разчитат на статични файлове.
340+
341+
### Оценяване
342+
343+
Решението може да ви донесе до 100 точки, като ще бъде оценявано за:
344+
345+
* функционална пълнота и коректност, и за автоматични тестове с добър code coverage (60% от оценката)
346+
* добър обектно-ориентиран дизайн, спазване на правилата за чист код и подбиране на оптимални за задачата структури от данни (40% от оценката)
347+
348+
### 🤖 Отговорно използване на AI и академична почтеност
349+
350+
Използването на генеративни AI инструменти (като GitHub Copilot, ChatGPT и др.) е допустимо единствено с цел подпомагане на процеса на учене, но не и като заместител на самостоятелното мислене и работа. Всеки студент носи пълна отговорност за разбирането, тестването и обяснението на кода, който предава. Представянето на код, който е очевидно автоматично генериран или който не можете да обясните и защитите устно или писмено, ще се счита за форма на недопустимо подпомагане или плагиатство, съгласно правилата на курса и университета. Ако сте използвали AI, посочете това в документацията – уточнете кои части са генерирани, с каква цел, и опишете накратко как работят и как сте проверили тяхната коректност, по същия начин, по който бихте цитирали външен източник. Целта на тази политика е да насърчи отговорната и критична употреба на съвременни инструменти, задълбоченото разбиране на материала и поддържането на високи стандарти на академична почтеност.
351+
352+
### Желаем ви успех! :four_leaf_clover:

0 commit comments

Comments
 (0)