From a52dd1e8c3e3366dfd5fc30dfe8339a646227252 Mon Sep 17 00:00:00 2001 From: Dao <comnuoc@users.noreply.gitlab.utu.fi> Date: Sun, 23 Jun 2024 03:19:01 +0300 Subject: [PATCH] feat(part2): Exercise 2. --- part_2/exercise_2/.gitignore | 30 +++++++ part_2/exercise_2/exercise_2.iml | 11 +++ part_2/exercise_2/src/Book.java | 103 ++++++++++++++++++++++ part_2/exercise_2/src/BookDto.java | 2 + part_2/exercise_2/src/BookRepository.java | 22 +++++ part_2/exercise_2/src/BookService.java | 49 ++++++++++ part_2/exercise_2/src/Id.java | 7 ++ part_2/exercise_2/src/NotEmptyString.java | 26 ++++++ part_2/exercise_2/src/User.java | 23 +++++ part_2/exercise_2/src/UserProvider.java | 8 ++ part_2/exercise_2/src/Year.java | 21 +++++ 11 files changed, 302 insertions(+) create mode 100644 part_2/exercise_2/.gitignore create mode 100644 part_2/exercise_2/exercise_2.iml create mode 100644 part_2/exercise_2/src/Book.java create mode 100644 part_2/exercise_2/src/BookDto.java create mode 100644 part_2/exercise_2/src/BookRepository.java create mode 100644 part_2/exercise_2/src/BookService.java create mode 100644 part_2/exercise_2/src/Id.java create mode 100644 part_2/exercise_2/src/NotEmptyString.java create mode 100644 part_2/exercise_2/src/User.java create mode 100644 part_2/exercise_2/src/UserProvider.java create mode 100644 part_2/exercise_2/src/Year.java diff --git a/part_2/exercise_2/.gitignore b/part_2/exercise_2/.gitignore new file mode 100644 index 0000000..d578053 --- /dev/null +++ b/part_2/exercise_2/.gitignore @@ -0,0 +1,30 @@ +### IntelliJ IDEA ### +/.idea/ +out/ +!**/src/main/**/out/ +!**/src/test/**/out/ + +### Eclipse ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache +bin/ +!**/src/main/**/bin/ +!**/src/test/**/bin/ + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ + +### VS Code ### +.vscode/ + +### Mac OS ### +.DS_Store \ No newline at end of file diff --git a/part_2/exercise_2/exercise_2.iml b/part_2/exercise_2/exercise_2.iml new file mode 100644 index 0000000..c90834f --- /dev/null +++ b/part_2/exercise_2/exercise_2.iml @@ -0,0 +1,11 @@ +<?xml version="1.0" encoding="UTF-8"?> +<module type="JAVA_MODULE" version="4"> + <component name="NewModuleRootManager" inherit-compiler-output="true"> + <exclude-output /> + <content url="file://$MODULE_DIR$"> + <sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" /> + </content> + <orderEntry type="inheritedJdk" /> + <orderEntry type="sourceFolder" forTests="false" /> + </component> +</module> \ No newline at end of file diff --git a/part_2/exercise_2/src/Book.java b/part_2/exercise_2/src/Book.java new file mode 100644 index 0000000..becf5e0 --- /dev/null +++ b/part_2/exercise_2/src/Book.java @@ -0,0 +1,103 @@ +import java.util.Objects; + +/* + * @.classInvariant: + * getId() != null + * && getTitle() != null + * && getYear() != null + * && getPublisher() != null + */ +public class Book { + private final Id id; + private NotEmptyString title; + private Year year; + private NotEmptyString publisher; + + /* + * @.pre: true + * @.post: Book object is constructed, throw NullPointerException if one of id, title, year, or publisher is null. + */ + public Book(Id id, NotEmptyString title, Year year, NotEmptyString publisher) { + this.id = Objects.requireNonNull(id, "Id should not be null"); + + setTitle(title); + setYear(year); + setPublisher(publisher); + } + + /* + * @.pre: true + * @.post: RESULT != null + */ + public Id getId() { + return id; + } + + /* + * @.pre: true + * @.post: RESULT != null + */ + public NotEmptyString getTitle() { + return title; + } + + /* + * @.pre: true + * @.post: RESULT != null + */ + public Year getYear() { + return year; + } + + /* + * @.pre: true + * @.post: RESULT != null + */ + public NotEmptyString getPublisher() { + return publisher; + } + + /* + * @.pre: true + * @.post: data is updated. + */ + protected void updateData(BookDto dto) { + Objects.requireNonNull(dto, "Id should not be null"); + + if (null != dto.title()) { + setTitle(dto.title()); + } + + if (null != dto.year()) { + setYear(dto.year()); + } + + if (null != dto.publisher()) { + setPublisher(dto.publisher()); + } + } + + /* + * @.pre: true + * @.post: data is assigned, throw NullPointerException if year is null. + */ + private void setYear(Year year) { + this.year = Objects.requireNonNull(year, "Year should not be null"); + } + + /* + * @.pre: true + * @.post: data is assigned, throw NullPointerException if title is null. + */ + private void setTitle(NotEmptyString title) { + this.title = Objects.requireNonNull(title, "Title should not be null"); + } + + /* + * @.pre: true + * @.post: data is assigned, throw NullPointerException if publisher is null. + */ + private void setPublisher(NotEmptyString publisher) { + this.publisher = Objects.requireNonNull(publisher, "Publisher should not be null"); + } +} diff --git a/part_2/exercise_2/src/BookDto.java b/part_2/exercise_2/src/BookDto.java new file mode 100644 index 0000000..a439bc1 --- /dev/null +++ b/part_2/exercise_2/src/BookDto.java @@ -0,0 +1,2 @@ +public record BookDto(Id id, NotEmptyString title, Year year, NotEmptyString publisher) { +} diff --git a/part_2/exercise_2/src/BookRepository.java b/part_2/exercise_2/src/BookRepository.java new file mode 100644 index 0000000..f592cb5 --- /dev/null +++ b/part_2/exercise_2/src/BookRepository.java @@ -0,0 +1,22 @@ +import java.util.List; + +public interface BookRepository { + /* + * @.pre: Id != null + * @.post: RESULT != null, + * throw RuntimeException if book is not found + */ + Book find(Id id); + + /* + * @.pre: BookDto != null + * @.post: Books are returned based on the search criteria + */ + List<Book> search(BookDto criteria); + + /* + * @.pre: Book != null + * @.post: book is updated + */ + void update(Book book); +} diff --git a/part_2/exercise_2/src/BookService.java b/part_2/exercise_2/src/BookService.java new file mode 100644 index 0000000..0eca39f --- /dev/null +++ b/part_2/exercise_2/src/BookService.java @@ -0,0 +1,49 @@ +import java.util.List; +import java.util.Objects; + +public class BookService { + private final BookRepository bookRepository; + private final UserProvider userProvider; + + public BookService(BookRepository bookRepository, UserProvider userProvider) { + this.bookRepository = bookRepository; + this.userProvider = userProvider; + } + + /* + * @.pre: true + * @.post: Books are returned based on the search criteria, throw SecurityException if user is not allowed to search + */ + public List<Book> search(BookDto criteria) { + Objects.requireNonNull(criteria, "Criteria should not be null"); + + User user = userProvider.getUser(); + + if (!user.canSearch()) { + throw new SecurityException("User is not allowed to search books."); + } + + return bookRepository.search(criteria); + } + + /* + * @.pre: true + * @.post: Book is updated, + * throw SecurityException if user is not allowed to update + */ + public void update(BookDto dto) { + Objects.requireNonNull(dto, "Book data should not be null"); + Objects.requireNonNull(dto.id(), "Book id should not be null"); + + Book book = bookRepository.find(dto.id()); + User user = userProvider.getUser(); + + if (!user.canUpdate(book)) { + throw new SecurityException("User is not allowed to update book."); + } + + book.updateData(dto); + + bookRepository.update(book); + } +} \ No newline at end of file diff --git a/part_2/exercise_2/src/Id.java b/part_2/exercise_2/src/Id.java new file mode 100644 index 0000000..b96bd5f --- /dev/null +++ b/part_2/exercise_2/src/Id.java @@ -0,0 +1,7 @@ +import java.util.Objects; + +public record Id(Object id) { + public Id { + Objects.requireNonNull(id, "Id should not be null"); + } +} diff --git a/part_2/exercise_2/src/NotEmptyString.java b/part_2/exercise_2/src/NotEmptyString.java new file mode 100644 index 0000000..55a4263 --- /dev/null +++ b/part_2/exercise_2/src/NotEmptyString.java @@ -0,0 +1,26 @@ +/* + * @.classInvariant: + * str() != null && !str.trim().isEmpty() + */ +public record NotEmptyString(String str) { + /* + * @.pre: true + * @.post: Object is constructed, throw NotEmptyStringException if string is empty + */ + public NotEmptyString { + if (null == str || str.trim().isEmpty()) { + throw new NotEmptyStringException("String is null or empty."); + } + } + + @Override + public String toString() { + return str; + } +} + +class NotEmptyStringException extends RuntimeException { + public NotEmptyStringException(String message) { + super(message); + } +} diff --git a/part_2/exercise_2/src/User.java b/part_2/exercise_2/src/User.java new file mode 100644 index 0000000..4a4988e --- /dev/null +++ b/part_2/exercise_2/src/User.java @@ -0,0 +1,23 @@ +public interface User { + /* + * @.pre: true + * @.post: whether user is a staff or not + */ + boolean isStaff(); + + /* + * @.pre: true + * @.post: RESULT == true + */ + default boolean canSearch() { + return true; + } + + /* + * @.pre: true + * @.post: RESULT == true if user is a staff, else RESULT == false + */ + default boolean canUpdate(Book book) { + return this.isStaff(); + } +} diff --git a/part_2/exercise_2/src/UserProvider.java b/part_2/exercise_2/src/UserProvider.java new file mode 100644 index 0000000..8e756d5 --- /dev/null +++ b/part_2/exercise_2/src/UserProvider.java @@ -0,0 +1,8 @@ +public interface UserProvider { + /* + * @.pre: true + * @.post: RESULT != null, + * throw Exception if no user + */ + User getUser(); +} diff --git a/part_2/exercise_2/src/Year.java b/part_2/exercise_2/src/Year.java new file mode 100644 index 0000000..c2ea489 --- /dev/null +++ b/part_2/exercise_2/src/Year.java @@ -0,0 +1,21 @@ +/* + * @.classInvariant: + * year() > 0 && year() <= 9999 + */ +public record Year(int year) { + /* + * @.pre: true + * @.post: Object is constructed, throw InvalidYearException if year <= 0 || year > 9999 + */ + public Year { + if (year <= 0 || year > 9999) { + throw new InvalidYearException("Year is not valid"); + } + } +} + +class InvalidYearException extends RuntimeException { + public InvalidYearException(String message) { + super(message); + } +} \ No newline at end of file -- GitLab