diff --git a/.gitignore b/.gitignore index a1c2a23..188fae8 100644 --- a/.gitignore +++ b/.gitignore @@ -1,23 +1,7 @@ -# Compiled class file -*.class +.idea +out +*.iml +log -# Log file -*.log -# BlueJ files -*.ctxt -# Mobile Tools for Java (J2ME) -.mtj.tmp/ - -# Package Files # -*.jar -*.war -*.nar -*.ear -*.zip -*.tar.gz -*.rar - -# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml -hs_err_pid* diff --git a/README.md b/README.md new file mode 100644 index 0000000..1fdcab2 --- /dev/null +++ b/README.md @@ -0,0 +1,202 @@ +# [Курс BaseJava (обновленный и переработанный)](http://javaops.ru/reg/basejava) + +## Разработка web-приложения "База данных резюме" + +В данном курсе вы создадите с нуля web-приложение, реализуя разные способы хранения резюме. Проект включает в себя следующее: + - **Технологии:** Java 8, GitHub/Git, JUnit, Logging, GSON, JAXB, SQL, PostgreSQL, Сервлеты, HTML, JSP, JSTL, Tomcat, Maven и многое другое + - **Различные способы реализации хранения резюме:** + - в сортированном и не сортированном массиве + - в коллекциях (List, Map) + - в файловой системе: + - с использованием File и Path API + - в стандартной и кастомной сериализации Java + - в формате JSON ([Google Gson](https://en.wikipedia.org/wiki/Gson)) + - в формате XML ([JAXB](https://ru.wikipedia.org/wiki/Java_Architecture_for_XML_Binding)) + - в реляционной базе [PostgreSQL](https://ru.wikipedia.org/wiki/PostgreSQL) + - **Установку (деплой) web-приложения:** + - в контейнер сервлетов [Tomcat](https://ru.wikipedia.org/wiki/Apache_Tomcat) + - в облачный сервис [Heroku](https://ru.wikipedia.org/wiki/Heroku) + +> Любое знание стоит воспринимать как подобие семантического дерева: убедитесь в том, что понимаете фундаментальные принципы, то есть ствол и крупные ветки, прежде чем лезть в мелкие листья-детали. Иначе последним не на чем будет держаться + +— Илон Маск + +### Изучите [бесплатный урок](lesson/lesson1.md). В конце урока находится домашнее задание, по которому можно оценить свой уровень готовности к проекту + +## Программа курса + +#### Занятие 1 (бесплатное) + - Обзор курса и методики обучения + - Подготовка и настройка рабочего окружения + - Подходы, применяемые при разработке ПО + - Обзор инструментов и технологий, используемых Java-разработчиками + - Введение в язык Java: история создания, JDK, JVM, JRE, JIT-компиляция + - Системы управления версиями. Git + - Домашнее задание + +#### Занятие 2 + - Типы данных + - Введение в объектно-ориентированное программирование + - Принципы ООП + - Классы и объекты + - Классы-обертки + - Модификаторы доступа + - Конструктор + - Структура памяти java-программы: Heap (куча), Stack (стек) + - Пакеты + - Домашнее задание + +#### Занятие 3 + - Разбор домашнего задания + - Обзор суперкласса Object + - Связь между equals() и hashCode() + - Статические методы и переменные + - Программирование с помощью интерфейсов + - Абстрактные классы + - Сложность алгоритмов + - Паттерн проектирования Template Method + - Домашнее задание + +#### Занятие 4 + - Разбор домашнего задания + - Конструктор + - Работа со строками: String, StringBuilder, StringBuffer + - String literal pool + - Исключения (Exceptions) + - Ключевые слова: this, super + - Reflection + - Аннотации + - Введение в модульное тестирование. JUnit + - Домашнее задание + +#### Занятие 5 + - Разбор домашнего задания + - Коллекций. Иерархия классов + - Списки (List) + - Множества (Set) + - Ассоциативные массивы (Map) + - Введение в Iterator + - Домашнее задание + +#### Занятие 6 + - Разбор домашнего задания + - Паттерн проектирования Iterator + - Autoboxing и Unboxing + - Вложенные классы + - Внутренние классы + - Локальные классы + - Анонимные классы + - Введение в лямбда-выражения + - Функциональный интерфейс + - Домашнее задание + +#### Занятие 7 + - Разбор домашнего задания + - Дженерики (Generic) + - Введение в логирование. Log4J, Java Logging API + - Паттерн проектирования Singleton + - Перечисления (Enum) + - Объектная модель + - Домашнее задание + +#### Занятие 8 + - Разбор домашнего задания + - Классы работы с датами: Date, Calendar, TimeZone + - Дата и время в Java 8+ + - File API + - Освобождение ресурсов: try-with-resources + - Домашнее задание + +#### Занятие 9 + - Разбор домашнего задания + - Обзор пакета java.io + - Классы чтения/записи потоков: InputStream и OutputStream + - Паттерн проектирования Decorator + - Классы чтения/записи символов: Reader и Writer + - Сериализация объектов + - Обзор пакета java.nio + - Введение в Java 8+ Stream API + - Домашнее задание + +#### Занятие 10 + - Разбор домашнего задания + - Паттерн проектирования Strategy + - Работа с XML (JAXB) + - Работа с JSON (GSON) + - Классы чтения/записи примитивных типов: DataInputStream и DataOutputStream + - Домашнее задание + +#### Занятие 11 + - Многопоточность + - Закон Мура и Амдала + - Потоки. Синхронизация доступа + - Обзор методов класса Object + - Ленивая инициализация + - Java Memory Model + - Deadlock + - Домашнее задание + +#### Занятие 12 + - Разбор домашнего задания + - Обзор классов java.util.concurrent + - Синхронизаторы + - ThreadLocal-переменные + - Сравнение с обменом (Compare-and-swap) + - Домашнее задание + +#### Занятие 13 + - Разбор домашнего задания + - Введение в реляционные базы данных + - Язык SQL + - Обзор NoSQL баз данных + - Установка и настройка СУБД PostgreSQL + - Работа с базами данных из IDEA + - Конфигурирование базы данных и каталога хранения + - Подключение базы данных к проекту + - Обзор JDBC-архитектуры + - Домашнее задание + +#### Занятие 14 + - Разбор домашнего задания + - Операции соединения таблиц. JOIN + - Транзакции + - Требования к транзакциям. ACID + - Уровни изоляции транзакций в SQL + - Установка и настройка контейнера сервлетов Tomcat + - Домашнее задание + +#### Занятие 15 + - Разбор домашнего задания + - Введение в HTML + - Основы протокола HTTP + - Настройка web.xml + - Деплой web-приложения в Tomcat + - Сервлеты + - Домашнее задание + +#### Занятие 16 + - Разбор домашнего задания + - Жизненный цикл сервлета + - Создание динамических страниц. JSP + - Расширенные возможности JSP. JSTL + - Redirect и Forward + - CRUD-операции + - Домашнее задание + +#### Занятие 17 + - Разбор домашнего задания + - Деплой приложения в облачный сервис Heroku + - Загрузка классов в Java. Classloader + - Домашнее задание + +## Рекомендуемые книги +- [Яков Файн, "Программирование на Java для начинающих"](http://myflex.org/books/java4kids/java4kids.htm) +- [Книги по Java: от новичка до профессионала](https://proglib.io/p/java-books-2019/) +- [Джошуа Блох, "Java. Эффективное программирование, 3-е издание"](https://www.ozon.ru/context/detail/id/148627191/) +- [Роберт Мартин, "Чистый код"](https://www.ozon.ru/context/detail/id/142429922/) +- [Серия Head First, "Паттерны проектирования"](https://www.ozon.ru/context/detail/id/144233005/) +- [Вайсфельд Мэтт, "Объектно-ориентированный подход"](https://www.ozon.ru/context/detail/id/166375103/?stat=YW5fMQ%3D%3D) + +## Ресурсы в сети +- [EduTools плагин от JetBrains для изучения Kotlin, Java, Python, Scala и других языков](http://javaops.ru/view/story/story21#edutools) +- [JetBrains Academy — интерактивный учебный курс по Java](https://www.jetbrains.com/ru-ru/academy/) diff --git a/config/init_db.sql b/config/init_db.sql new file mode 100644 index 0000000..88bad82 --- /dev/null +++ b/config/init_db.sql @@ -0,0 +1,13 @@ +CREATE TABLE resume ( + uuid CHAR(36) PRIMARY KEY NOT NULL, + full_name TEXT NOT NULL +); + +CREATE TABLE contact ( + id SERIAL, + resume_uuid CHAR(36) NOT NULL REFERENCES resume (uuid) ON DELETE CASCADE, + type TEXT NOT NULL, + value TEXT NOT NULL +); +CREATE UNIQUE INDEX contact_uuid_type_index + ON contact (resume_uuid, type); \ No newline at end of file diff --git a/config/resumes.properties b/config/resumes.properties new file mode 100644 index 0000000..f1468cf --- /dev/null +++ b/config/resumes.properties @@ -0,0 +1,5 @@ +storage.dir=D:/YandexDisk/_java_learn/storage_files/full +storage.emptyDir=D:/YandexDisk/_java_learn/storage_files/empty +db.url=jdbc:postgresql://localhost:5432/resumes +db.user=postgres +db.password=postgres \ No newline at end of file diff --git a/lesson/lesson1.md b/lesson/lesson1.md new file mode 100644 index 0000000..73fc452 --- /dev/null +++ b/lesson/lesson1.md @@ -0,0 +1,111 @@ +# Первое занятие + +## [Демо разрабатываемого приложения](https://basejava.herokuapp.com/resume?theme=light) + +## ![video](https://cloud.githubusercontent.com/assets/13649199/13672715/06dbc6ce-e6e7-11e5-81a9-04fbddb9e488.png) [Видеообзор курса Basejava](https://www.youtube.com/watch?v=0ydTRfKS9yY) + +### Подготовка рабочего окружения +- Установите [JDK8](http://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html) (выбрать Accept License Agreement) +- Установите систему управления версиями [Git](http://git-scm.com/downloads) +- Создайте аккаунт на [GitHub](https://github.com/) +- Для удобной навигации по файлам на GitHub установите расширение для браузера [Octotree](https://habrahabr.ru/post/223527/) +- Установите [IntelliJ IDEA Community](http://www.jetbrains.com/idea/download/index.html) (Ultimate-версия понадобится позже, при работе с базой данных и web) +> Для версии Ultimate дается 30 дней бесплатного использования. Но в качестве подарка, каждому участнику курса, мы выдаем единоразово купон на 6 месяцев + +## ![video](https://cloud.githubusercontent.com/assets/13649199/13672715/06dbc6ce-e6e7-11e5-81a9-04fbddb9e488.png) 1. [Разработка ПО](https://drive.google.com/file/d/0B_4NpoQW1xfpVjZUTEpvVUN1TTA/view?usp=sharing&resourcekey=0-hnn1HIBU3WIuDMVuQAxA8w) +- [Мифический человеко-месяц](https://ru.wikipedia.org/wiki/Мифический_человеко-месяц) +- [Подборка книг для руководителей в сфере IT](https://habr.com/ru/company/skyeng/blog/465215/) +- [Размеры проектов в количестве строк кода](https://www.freecodecamp.org/news/the-biggest-codebases-in-history-a128bb3eea73) +- [Соглашения по оформлению кода](https://topjava.ru/blog/google-java-style-guide) +- [Методологии разработки ПО](https://dou.ua/forums/topic/14015/) +- [Ещё раз про семь основных методологий разработки](https://habrahabr.ru/company/edison/blog/269789/) + +## ![video](https://cloud.githubusercontent.com/assets/13649199/13672715/06dbc6ce-e6e7-11e5-81a9-04fbddb9e488.png) 2. [Обзор инструментов и технологий](https://drive.google.com/file/d/0B_4NpoQW1xfpTXJYU2xZbjN2d2M/view?usp=sharing&resourcekey=0-Uw_lRGW12YNjwY7phXzVdg) +- [Популярность Java-технологий в 2019 году](https://topjava.ru/blog/sostoyanie-java-v-2019-godu) +- [Java Technology Report 2021](https://www.jrebel.com/blog/2021-java-technology-report) +- [The State of Developer Ecosystem 2020](https://www.jetbrains.com/lp/devecosystem-2020/java/) +- [JVM Ecosystem Report 2021](https://snyk.io/jvm-ecosystem-report-2021/) + +## ![video](https://cloud.githubusercontent.com/assets/13649199/13672715/06dbc6ce-e6e7-11e5-81a9-04fbddb9e488.png) 3. [Обзор языка Java](https://drive.google.com/file/d/0B_4NpoQW1xfpTU5SSElhUjlGNnc/view?usp=sharing&resourcekey=0-DOyRoGhREx2kvKwAKTOlYg) + +![jvm](https://cloud.githubusercontent.com/assets/18701152/15219296/e6c67e86-186b-11e6-986f-651a87deec6c.png) + +- [Java](http://ru.wikipedia.org/wiki/Java), [JVM](http://ru.wikipedia.org/wiki/Виртуальная_машина_Java), [JIT-компиляция](http://ru.wikipedia.org/wiki/JIT) +- Java [ME](http://ru.wikipedia.org/wiki/Java_Platform,_Micro_Edition), [SE](https://ru.wikipedia.org/wiki/Java_Platform,_Standard_Edition), [EE](http://ru.wikipedia.org/wiki/Java_Platform,_Enterprise_Edition) +- [Что такое JDK? Введение в средства разработки Java](https://topjava.ru/blog/what-is-the-jdk) +- [Что такое JRE? Введение в среду выполнения Java](https://topjava.ru/blog/what-is-the-jre) +- [Что такое Java? История создания](http://www.intuit.ru/studies/courses/16/16/lecture/27105) +- [Programming languages TIOBE Index](http://www.tiobe.com/index.php/content/paperinfo/tpci/index.html) +- [Java Microbenchmark JMH](http://openjdk.java.net/projects/code-tools/jmh/) (используем на курсе [MasterJava](https://github.com/JavaWebinar/masterjava#Занятие-2)) +- [Руководство по массивам в Java](https://topjava.ru/blog/rukovodstvo-po-massivam-v-java-ch1) + +## ![video](https://cloud.githubusercontent.com/assets/13649199/13672715/06dbc6ce-e6e7-11e5-81a9-04fbddb9e488.png) 4. [Системы управления версиями. Git](https://drive.google.com/file/d/0B9Ye2auQ_NsFSUNrdVc0bDZuX2s/edit?resourcekey=0-6scb0PBj2A3Oqf6rsU2egQ) + +![image](https://cloud.githubusercontent.com/assets/18701152/15219746/9295a2fe-186d-11e6-876b-c61cc9be71e4.png) + +- [Система управления версиями (VCS)](https://ru.wikipedia.org/wiki/Система_управления_версиями) +- [Введение в Git: от установки до основных команд](https://tproger.ru/translations/beginner-git-cheatsheet/) +- [Сравнение разных VCS](https://biz30.timedoctor.com/ru/cистема-контроля-версий/) +- [Видео-уроки по Git](https://www.youtube.com/playlist?list=PLDyvV36pndZHkDRik6kKF6gSb0N0W995h) +- Интерактивные Git-обучалки: [1](https://githowto.com/ru), [2](http://learngitbranching.js.org) +- [Официальная книга по Git](https://git-scm.com/book/ru/v2) + +### Настройка проекта +- Создайте на GitHub репозиторий с названием `basejava` +- Откройте консоль у себя на компьютере (в папке, где планируете разместить проект) и выполните следующее: + - **Скачайте копию проекта с заранее заданными классами: `git clone https://github.com/JavaOps/basejava.git`. Реализацию дз выполняйте в рамках данной копии** + - Перейдите в каталог проекта: `cd basejava` + - Настройте git у себя на компьютере на свой репозиторий в GitHub: + - `git remote set-url origin url_на_ваш_basejava-репозиторий.git` — настройка pull + - `git remote set-url --push origin url_на_ваш_basejava-репозиторий.git` — настройка push + - `git remote -v` — удостоверьтесь, что команда выводит ссылки на ваш удаленный репозиторий + - `git push -u origin master` — [устанавливаем связь](https://qna.habr.com/q/118865) между локальной и удаленной веткой master + +## Домашнее задание HW1 +- Откройте в IntelliJ IDEA ваш проект, выбрав каталог `basejava`, который вы скачали ранее к себе на компьютер: + +![Screenshot_5](https://user-images.githubusercontent.com/29703461/199550057-fce7cf3c-7040-422f-b490-7b85b47ae952.png) + +- Реализуйте методы `save, get, delete, clear, getAll, size` в классе `ArrayStorage`, организовав хранение резюме в массиве +- Храните все резюме в начале `storage` (без пустот в виде `null`), чтобы не перебирать каждый раз все 10_000 элементов +- При реализации метода `delete` учитывайте, что после удаления резюме между оставшимися резюме не должно быть пустых ячеек, заполненных null +``` +Схема хранения резюме в массиве storage (в элементах от 0 до size-1 отсутствуют null): + +r1, r2, r3,..., rn, null, null,..., null +<----- size -----> +<------- storage.length (10000) -------> +``` +- Проверьте вашу реализацию с помощью классов `MainArray.main()` и `MainTestArrayStorage.main()` +- Изучите дополнительные материалы по IntelliJ IDEA: + - [Idea Wiki](https://github.com/JavaOPs/topjava/wiki/IDEA) + - [Отладка Java кода в IDEA. Основные возможности отладчика](https://youtu.be/Z1BQsf0A4xY) + - [Эффективная работа с кодом в IntelliJ IDEA](https://www.youtube.com/watch?v=tpv5n2jWHlw) + - [Эффективная работа в IDEA](https://www.youtube.com/watch?v=_rj7dx6c5R8) + +### Вопросы по HW1 + > Не могу запустить программу, да и рядом с классами появился какой-то значок + + ![badsrc](https://user-images.githubusercontent.com/29703461/38277015-9cd9155e-379f-11e8-9cd4-a9182a005e9a.png) + - Проблема в том, что IDEA неправильно "воспринимает" папку `src`. Для ее решения необходимо нажать `ПКМ на папке src -> выбрать Mark Directory as -> Sources Root` + +### Замечания по выполнению HW1 +1. Все резюме в хранилище имеют уникальный `uuid`, что исключает повторы. Сортировка по `uuid` не требуется +1. Давайте осмысленные комментарии коммитам +1. Перед каждым коммитом не забывайте пользоваться сочетанием клавиш `Ctrl + Alt + L` (автоматическое форматирование кода) +1. Удаляйте в классах неиспользуемые импорты (`Ctrl + Alt + O`) +1. Не игнорируй подсказки IDEA (подсвечивает) +1. В методе `clear()` обнуление массива предполагает обнуление (null) ячеек, где хранятся Resume, а не создание нового или присваивание ему null +1. При реализации методов не используйте коллекции +1. Не меняйте сигнатуры методов в `ArrayStorage` +1. Не добавляйте в `Resume` новые поля +1. Resume r — давайте переменным осмысленные имена, например resume. r допустимо в коротких циклах и лямбда-выражениях + +## ![video](https://cloud.githubusercontent.com/assets/13649199/13672715/06dbc6ce-e6e7-11e5-81a9-04fbddb9e488.png) 5. [Вебинар "Быть программистом: от детства к зрелости"](https://www.youtube.com/watch?v=D5Hej0TyLaU) + - [Слайды вебинара](https://docs.google.com/presentation/d/1YwtCCZsaGMdl-V15kTDHiJxiS52IAl-qqheNPpiNr54/) +### Советы по обучению + - Учитесь грамотно формулировать проблему: "у меня не работает" может иметь тысячи причин. В процессе формулирования часто приходит ее решение + - Учитесь исследовать проблему. Внимательное чтение логов и умение дебажить — основные навыки разработчика. В логах необходимо читать верхнюю часть самого нижнего иксепшена. Именно там находится причина возникшей ошибки + - Грамотно распределяйте время для каждой проблемы. Не впадайте в крайности: сразу бросаться за помощью или биться над ней часами. Подходите к ее решению разумно + - Получайте в процессе решения обратную связь, что бы убедиться в правильности выбранного направления + - [Советы новичкам](http://blog.csssr.ru/2016/09/19/how-to-be-a-beginner-developer) diff --git a/src/com/learnjava/Config.java b/src/com/learnjava/Config.java new file mode 100644 index 0000000..74fda2e --- /dev/null +++ b/src/com/learnjava/Config.java @@ -0,0 +1,49 @@ +package com.learnjava; + +import com.learnjava.storage.SqlStorage; +import com.learnjava.storage.Storage; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.Properties; + +public class Config { + private static final File PROPS = new File("config\\resumes.properties"); + private static final Config INSTANCE = new Config(); + + private final File storageDir; + private final File storageDirEmpty; + private final Storage storage; + + + public static Config get() { + return INSTANCE; + } + + private Config() { + try (InputStream is = new FileInputStream(PROPS)) { + Properties props = new Properties(); + props.load(is); + storageDir = new File(props.getProperty("storage.dir")); + storageDirEmpty = new File(props.getProperty("storage.emptyDir")); + storage = new SqlStorage(props.getProperty("db.url"), props.getProperty("db.user"), props.getProperty("db.password")); + } catch (IOException e) { + throw new IllegalStateException("Invalid config file " + PROPS.getAbsolutePath()); + } + } + + public File getStorageDir() { + return storageDir; + } + + public File getStorageDirEmpty() { + return storageDirEmpty; + } + + public Storage getStorage() { + return storage; + } + +} diff --git a/src/com/learnjava/DeadLock.java b/src/com/learnjava/DeadLock.java new file mode 100644 index 0000000..37f7937 --- /dev/null +++ b/src/com/learnjava/DeadLock.java @@ -0,0 +1,35 @@ +package com.learnjava; + +public class DeadLock { + public static void main(String[] args) { + + final String resource1 = "Chicken"; + final String resource2 = "Egg"; + + Thread t1 = new Thread(() -> { + synchronized (resource1) { + System.out.println("Thread 1: locked " + resource1 ); + + try { Thread.sleep(100);} catch (Exception ignored) {} + + synchronized (resource2) { + System.out.println("Thread 1: locked" + resource1); + } + } + }); + + Thread t2 = new Thread(() -> { + synchronized (resource2) { + System.out.println("Thread 2: locked " + resource2); + + try { Thread.sleep(100);} catch (Exception ignored) {} + + synchronized (resource1) { + System.out.println("Thread 2: locked " + resource2); + } + } + }); + t1.start(); + t2.start(); + } +} diff --git a/src/com/learnjava/HomeWork12Exercise.java b/src/com/learnjava/HomeWork12Exercise.java new file mode 100644 index 0000000..e16f961 --- /dev/null +++ b/src/com/learnjava/HomeWork12Exercise.java @@ -0,0 +1,29 @@ +package com.learnjava; + +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +public class HomeWork12Exercise { + public static int minValue(int[] values) { + return Arrays.stream(values) + .sorted() + .distinct() + .reduce(0, (subResult, currentNum) -> subResult * 10 + currentNum); + } + + public static List oddOrEven(List integers) { + + Map> map = integers.stream() + .collect(Collectors.partitioningBy(num -> num % 2 == 0)); + return map.get(false).size() % 2 == 0 ? map.get(true) : map.get(false); + } + + public static void main(String[] args) { + int[] arr = {1, 1, 2, 1, 4, 5, 5, 9, 8}; + System.out.println("Минимальное значение по условиям задачи: " + minValue(arr)); + List intList = Arrays.asList(1, 2, 3, 7, 8, 3); + System.out.println("четные или не четные числа: " + oddOrEven(intList)); + } +} diff --git a/src/com/learnjava/MainArray.java b/src/com/learnjava/MainArray.java new file mode 100644 index 0000000..8aef8aa --- /dev/null +++ b/src/com/learnjava/MainArray.java @@ -0,0 +1,80 @@ +package com.learnjava; +import com.learnjava.model.*; +import com.learnjava.storage.*; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.util.List; + +/** + * Interactive test for ArrayStorage implementation + * (just run, no need to understand) + */ +public class MainArray { + private final static Storage ARRAY_STORAGE = new ArrayStorage(); + + public static void main(String[] args) throws IOException { + BufferedReader reader = new BufferedReader(new InputStreamReader(System.in)); + Resume r; + while (true) { + System.out.print("Введите одну из команд - (list | save name | delete uuid | get uuid | update uuid | clear | exit): "); + String[] params = reader.readLine().trim().toLowerCase().split(" "); + if (params.length < 1 || params.length > 3) { + System.out.println("Неверная команда."); + continue; + } + String param = null; + if (params.length > 1) { + param = params[1].intern(); + } + switch (params[0]) { + case "list": + printAll(); + break; + case "size": + System.out.println(ARRAY_STORAGE.size()); + break; + case "save": + r = new Resume(param); + ARRAY_STORAGE.save(r); + printAll(); + break; + case "update": + r = new Resume(param, params[2]); + ARRAY_STORAGE.update(r); + printAll(); + break; + case "delete": + ARRAY_STORAGE.delete(param); + printAll(); + break; + case "get": + System.out.println(ARRAY_STORAGE.get(param)); + break; + case "clear": + ARRAY_STORAGE.clear(); + printAll(); + break; + case "exit": + return; + default: + System.out.println("Неверная команда."); + break; + } + } + } + + static void printAll() { + List all = ARRAY_STORAGE.getAllSorted(); + System.out.println("----------------------------"); + if (all.size() == 0) { + System.out.println("Empty"); + } else { + for (Resume r : all) { + System.out.println(r); + } + } + System.out.println("----------------------------"); + } +} \ No newline at end of file diff --git a/src/com/learnjava/MainFile.java b/src/com/learnjava/MainFile.java new file mode 100644 index 0000000..38aa0ad --- /dev/null +++ b/src/com/learnjava/MainFile.java @@ -0,0 +1,56 @@ +package com.learnjava; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.util.Arrays; + +public class MainFile { + public static void main(String[] args) { + String filePath = ".\\.gitignore"; + + File file = new File(filePath); + try { + System.out.println(file.getCanonicalPath()); + } catch (IOException e) { + throw new RuntimeException("Error", e); + } + + File dir = new File("./src"); + System.out.println(dir.isDirectory()); + String[] list = dir.list(); + if (list != null) { + for (String name : list) { + System.out.println(name); + } + } + + try (FileInputStream fis = new FileInputStream(filePath)) { + System.out.println(fis.read()); + } catch (IOException e) { + throw new RuntimeException(e); + } + printDirectoryDeeply(dir,0); + } + + public static void printDirectoryDeeply(File dir, int deep) { + File[] files = dir.listFiles(); + int nextDeep = deep + 2; + char[] spaces = new char[deep]; + Arrays.fill(spaces, ' '); + + if (files != null) { + for (File file : files) { + if (file.isFile()) { + System.out.println("File: " + new String(spaces) + file.getName()); + } + } + for (File file : files) { + if (file.isDirectory()) { + System.out.println("Dir: " + new String(spaces) + file.getName()); + printDirectoryDeeply(file, nextDeep); + } + } + } + } +} diff --git a/src/com/learnjava/MainString.java b/src/com/learnjava/MainString.java new file mode 100644 index 0000000..4c0b65a --- /dev/null +++ b/src/com/learnjava/MainString.java @@ -0,0 +1,18 @@ +package com.learnjava; + +public class MainString { + public static void main(String[] args) { + String[] strArray = new String[]{"1", "2", "3", "4", "5"}; +// String result = ""; + StringBuilder sb = new StringBuilder(); + for (String str : strArray) { + sb.append(str).append(", "); + } + System.out.println(sb.toString()); + + String str1 = "abc"; + String str3 = "c"; + String str2 = ("ab" + str3).intern(); + System.out.println(str1 == str2); + } +} \ No newline at end of file diff --git a/src/com/learnjava/MainTestArrayStorage.java b/src/com/learnjava/MainTestArrayStorage.java new file mode 100644 index 0000000..bb67944 --- /dev/null +++ b/src/com/learnjava/MainTestArrayStorage.java @@ -0,0 +1,41 @@ +package com.learnjava; + +import com.learnjava.model.Resume; +import com.learnjava.storage.*; + +/** + * Test for your ArrayStorage implementation + */ +public class MainTestArrayStorage { + static final ArrayStorage ARRAY_STORAGE = new ArrayStorage(); + + public static void main(String[] args) { + Resume r1 = new Resume("uuid1"); + Resume r2 = new Resume("uuid2"); + Resume r3 = new Resume("uuid3"); + + ARRAY_STORAGE.save(r1); + ARRAY_STORAGE.save(r2); + ARRAY_STORAGE.save(r3); + + System.out.println("Get r1: " + ARRAY_STORAGE.get(r1.getUuid())); + System.out.println("Size: " + ARRAY_STORAGE.size()); + + System.out.println("Get dummy: " + ARRAY_STORAGE.get("dummy")); + + printAll(); + ARRAY_STORAGE.delete(r1.getUuid()); + printAll(); + ARRAY_STORAGE.clear(); + printAll(); + + System.out.println("Size: " + ARRAY_STORAGE.size()); + } + + static void printAll() { + System.out.println("\nGet All"); + for (Object r : ARRAY_STORAGE.getAllSorted()) { + System.out.println(r); + } + } +} \ No newline at end of file diff --git a/src/com/learnjava/ResumeTestData.java b/src/com/learnjava/ResumeTestData.java new file mode 100644 index 0000000..c600a1a --- /dev/null +++ b/src/com/learnjava/ResumeTestData.java @@ -0,0 +1,47 @@ +package com.learnjava; + +import com.learnjava.model.Resume; + +import java.util.UUID; + +import static com.learnjava.model.ContactType.*; +import static com.learnjava.model.SectionType.*; + +public class ResumeTestData { + + private static Resume resume; + + private ResumeTestData() { + } + + public static void printResumeTestData() { + System.out.println("uuid and full name: " + resume.toString()); + System.out.println(MOBILE.getTitle() + " : " + resume.getContact(MOBILE)); + System.out.println(SKYPE.getTitle() + " : " + resume.getContact(SKYPE)); + System.out.println(MAIL.getTitle() + " : " + resume.getContact(MAIL)); + System.out.println(OBJECTIVE.getTitle() + " : " + resume.getSection(OBJECTIVE).toString()); + System.out.println(ACHIEVEMENT.getTitle() + " : " + resume.getSection(ACHIEVEMENT).toString()); + System.out.println(EXPERIENCE.getTitle() + " : " + resume.getSection(EXPERIENCE).toString()); + } + + public static Resume getFilledResume(String uuid, String fullName) { + Resume resumeWData = new Resume(uuid, fullName); + resumeWData.addContact(MOBILE, "+7(921) 855-0482 "); + resumeWData.addContact(SKYPE, "skype:grigory.kislin"); + resumeWData.addContact(MAIL, "gkislin@yandex.ru"); + resumeWData.addContact(LINKEDIN, "linkedin.com/in/gkislin"); + resumeWData.addContact(GITHUB, "github.com/gkislin"); + resumeWData.addContact(STATCKOVERFLOW, "stackoverflow.com/users/548473"); + resumeWData.addContact(HOME_PAGE, "gkislin.ru/"); +// resumeWData.addSection(OBJECTIVE, new TextSection("Ведущий стажировок и корпоративного обучения по Java Web и Enterprise технологиям ")); +// resumeWData.addSection(SectionType.ACHIEVEMENT, new ListSection(Arrays.asList("Достижение1", "Достижение2", "Достижение3"))); +// resumeWData.addSection(SectionType.EXPERIENCE, new OrganizationSection(Arrays.asList( +// new Organization("Java Online Projects", "https://javaops.ru/", Arrays.asList(new Period(LocalDate.of(2013, 10, 1), LocalDate.now(), "Автор проекта.", "Создание, организация и проведение Java онлайн проектов и стажировок."))), new Organization("Wrike", "https://www.wrike.com/aa/", Arrays.asList(new Period(LocalDate.of(2014, 10, 1), LocalDate.of(2016, 1, 1), "Старший разработчик (backend)", "Проектирование и разработка онлайн платформы управления проектами Wrike (Java 8 API, Maven, Spring, MyBatis, Guava, Vaadin, PostgreSQL, Redis). Двухфакторная аутентификация, авторизация по OAuth1, OAuth2, JWT SSO.")))))); + return resumeWData; + } + + public void main(String[] args) { + resume = getFilledResume(UUID.randomUUID().toString(), "Grigoriy Kislin"); + printResumeTestData(); + } +} \ No newline at end of file diff --git a/src/com/learnjava/exception/ExistStorageException.java b/src/com/learnjava/exception/ExistStorageException.java new file mode 100644 index 0000000..fc0b090 --- /dev/null +++ b/src/com/learnjava/exception/ExistStorageException.java @@ -0,0 +1,7 @@ +package com.learnjava.exception; + +public class ExistStorageException extends StorageException { + public ExistStorageException(String uuid) { + super("Resume " + uuid + " already exist", uuid); + } +} \ No newline at end of file diff --git a/src/com/learnjava/exception/NotExistStorageException.java b/src/com/learnjava/exception/NotExistStorageException.java new file mode 100644 index 0000000..6429e6a --- /dev/null +++ b/src/com/learnjava/exception/NotExistStorageException.java @@ -0,0 +1,7 @@ +package com.learnjava.exception; + +public class NotExistStorageException extends StorageException { + public NotExistStorageException(String uuid) { + super("Resume " + uuid + " not exist", uuid); + } +} \ No newline at end of file diff --git a/src/com/learnjava/exception/StorageException.java b/src/com/learnjava/exception/StorageException.java new file mode 100644 index 0000000..850858f --- /dev/null +++ b/src/com/learnjava/exception/StorageException.java @@ -0,0 +1,32 @@ +package com.learnjava.exception; + + +public class StorageException extends RuntimeException { + private final String uuid; + + public StorageException(String message) { + this(message, null, null); + } + + public StorageException(String message, String uuid) { + super(message); + this.uuid = uuid; + } + + public StorageException(Exception e) { + this(e.getMessage(), e); + } + + public StorageException(String message, Exception e) { + this(message, null, e); + } + + public StorageException(String message, String uuid, Exception e) { + super(message, e); + this.uuid = uuid; + } + + public String getUuid() { + return uuid; + } +} \ No newline at end of file diff --git a/src/com/learnjava/model/ContactType.java b/src/com/learnjava/model/ContactType.java new file mode 100644 index 0000000..cde83ab --- /dev/null +++ b/src/com/learnjava/model/ContactType.java @@ -0,0 +1,22 @@ +package com.learnjava.model; + +public enum ContactType { + + MOBILE("Мобильный"), + SKYPE("Skype"), + MAIL("Почта"), + LINKEDIN("Профиль LinkedIn"), + GITHUB("Профиль GitHub"), + STATCKOVERFLOW("Профиль Stackoverflow"), + HOME_PAGE("Домашняя страница"); + + private final String title; + + ContactType(String title) { + this.title = title; + } + + public String getTitle() { + return title; + } +} \ No newline at end of file diff --git a/src/com/learnjava/model/Link.java b/src/com/learnjava/model/Link.java new file mode 100644 index 0000000..8809a3b --- /dev/null +++ b/src/com/learnjava/model/Link.java @@ -0,0 +1,50 @@ +package com.learnjava.model; + +import java.io.Serializable; +import java.util.Objects; + +public class Link implements Serializable { + private String name; + private String url; + + @SuppressWarnings("unused") + public Link() { + + } + public Link(String name, String url) { + this.name = name; + this.url = url; + } + + public String getName() { + return name; + } + + public String getUrl() { + return url; + } + + @Override + public String toString() { + return "Link(" + name + ',' + url + ')'; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + Link link = (Link) o; + + if (!name.equals(link.name)) return false; + return Objects.equals(url, link.url); + + } + + @Override + public int hashCode() { + int result = name.hashCode(); + result = 31 * result + (url != null ? url.hashCode() : 0); + return result; + } +} diff --git a/src/com/learnjava/model/ListSection.java b/src/com/learnjava/model/ListSection.java new file mode 100644 index 0000000..551068d --- /dev/null +++ b/src/com/learnjava/model/ListSection.java @@ -0,0 +1,42 @@ +package com.learnjava.model; + +import java.util.List; +import java.util.Objects; + +public class ListSection extends Section { + private List items; + + @SuppressWarnings("unused") + public ListSection() { + } + + public ListSection(List items) { + Objects.requireNonNull(items, "items must not be null"); + this.items = items; + } + + public List getItems() { + return items; + } + + @Override + public String toString() { + return items.toString(); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + ListSection that = (ListSection) o; + + return items.equals(that.items); + + } + + @Override + public int hashCode() { + return items.hashCode(); + } +} \ No newline at end of file diff --git a/src/com/learnjava/model/Organization.java b/src/com/learnjava/model/Organization.java new file mode 100644 index 0000000..4dbed15 --- /dev/null +++ b/src/com/learnjava/model/Organization.java @@ -0,0 +1,77 @@ +package com.learnjava.model; + +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; +import java.io.Serial; +import java.io.Serializable; +import java.util.List; + +@XmlAccessorType(XmlAccessType.FIELD) +public class Organization implements Serializable { + @Serial + private static final long serialVersionUID = 1L; + + private List periods; + private String Organization; + private Link homePage; + + public Organization(String name, String url, List periods) { + Organization = name; + homePage = new Link(name, url); + this.periods = periods; + } + + @SuppressWarnings("unused") + public Organization() { + } + @SuppressWarnings("unused") + public List getPeriods() { + return periods; + } + @SuppressWarnings("unused") + public void setPreiods(List periods) { + this.periods = periods; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + com.learnjava.model.Organization that = (com.learnjava.model.Organization) o; + + if (!Organization.equals(that.Organization)) return false; + return homePage.equals(that.homePage); + } + + @Override + public int hashCode() { + int result = homePage.hashCode(); + result = 31 * result + Organization.hashCode(); + result = 31 * result + homePage.hashCode(); + return result; + } + + @Override + public String toString() { + String periodText = "\n Organization{" + Organization + ", homePage=" + homePage; + //noinspection ResultOfMethodCallIgnored + periods.forEach(period -> periodText.concat( + ", startDate=" + period.startDate + + ", endDate=" + period.endDate + + ", title='" + period.title + "'" + + ", description='" + period.description + "'\n" + ) + ); + + return periodText; + } + + public Link getHomePage() { + return homePage; + } + + public List getPositions() { + return periods; + } +} \ No newline at end of file diff --git a/src/com/learnjava/model/OrganizationSection.java b/src/com/learnjava/model/OrganizationSection.java new file mode 100644 index 0000000..2cec527 --- /dev/null +++ b/src/com/learnjava/model/OrganizationSection.java @@ -0,0 +1,44 @@ +package com.learnjava.model; + +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; +import java.util.List; +import java.util.Objects; +@XmlAccessorType(XmlAccessType.FIELD) +public class OrganizationSection extends Section { + private List organizations; + + public OrganizationSection(List organizations) { + Objects.requireNonNull(organizations, "Experiences must not be null"); + this.organizations = organizations; + } + @SuppressWarnings("unused") + public OrganizationSection() { + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + OrganizationSection that = (OrganizationSection) o; + + return organizations.equals(that.organizations); + + } + + @Override + public int hashCode() { + return organizations.hashCode(); + } + + @Override + public String toString() { + return organizations.toString(); + } + + public List getOrganizations() { + return organizations; + } + +} \ No newline at end of file diff --git a/src/com/learnjava/model/Period.java b/src/com/learnjava/model/Period.java new file mode 100644 index 0000000..42bcc9d --- /dev/null +++ b/src/com/learnjava/model/Period.java @@ -0,0 +1,69 @@ +package com.learnjava.model; + +import com.learnjava.util.LocalDateAdapter; + +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter; +import java.io.Serializable; +import java.time.LocalDate; +import java.util.Objects; + + +@XmlAccessorType(XmlAccessType.FIELD) +public class Period implements Serializable { + @XmlJavaTypeAdapter(LocalDateAdapter.class) + public LocalDate startDate; + @XmlJavaTypeAdapter(LocalDateAdapter.class) + public LocalDate endDate; + + public String title; + public String description; + + public Period(LocalDate startDate, LocalDate endDate, String title, String description) { + this.startDate = startDate; + this.endDate = endDate; + this.title = title; + this.description = description; + } + @SuppressWarnings("unused") + public Period() { + } + + public LocalDate getStartDate() { + return startDate; + } + + public LocalDate getEndDate() { + return endDate; + } + + public String getTitle() { + return title; + } + + public String getDescription() { + return description; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Period position = (Period) o; + return Objects.equals(startDate, position.startDate) && + Objects.equals(endDate, position.endDate) && + Objects.equals(title, position.title) && + Objects.equals(description, position.description); + } + + @Override + public int hashCode() { + return Objects.hash(startDate, endDate, title, description); + } + + @Override + public String toString() { + return "Position(" + startDate + ',' + endDate + ',' + title + ',' + description + ')'; + } +} diff --git a/src/com/learnjava/model/Resume.java b/src/com/learnjava/model/Resume.java new file mode 100644 index 0000000..50949be --- /dev/null +++ b/src/com/learnjava/model/Resume.java @@ -0,0 +1,102 @@ +package com.learnjava.model; + + +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.XmlRootElement; +import java.io.Serial; +import java.io.Serializable; +import java.util.EnumMap; +import java.util.Map; +import java.util.Objects; +import java.util.UUID; + +/** + * ru.javawebinar.basejava.model.Resume class + */ + +@XmlRootElement +@XmlAccessorType(XmlAccessType.FIELD) +public class Resume implements Comparable, Serializable { + @Serial + private static final long serialVersionUID = 1L; + // Unique identifier + private String uuid; + private String fullName; + + @SuppressWarnings("FieldMayBeFinal") + private Map contacts = new EnumMap<>(ContactType.class); + @SuppressWarnings("FieldMayBeFinal") + private Map sections = new EnumMap<>(SectionType.class); + + public Resume(String fullName) { + this(UUID.randomUUID().toString(), fullName); + } + + public Resume() { + + } + + public Resume(String uuid, String fullName) { + this.uuid = uuid; + this.fullName = fullName; + } + + public String getFullName() { + return fullName; + } + + public Map getContacts() { + return contacts; + } + + public String getContact(ContactType type) { + return contacts.get(type); + } + + public Section getSection(SectionType type) { + return sections.get(type); + } + + public Map getSections() { + return sections; + } + + public void addContact(ContactType type, String value) { + contacts.put(type, value); + } + + public void addSection(SectionType type, Section section) { + sections.put(type, section); + } + + + public String getUuid() { + return uuid; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Resume resume = (Resume) o; + if (!uuid.equals(resume.uuid)) return false; + return fullName.equals(resume.fullName); + } + + @Override + public int hashCode() { + return Objects.hash(this.uuid, this.fullName); + } + + @Override + public String toString() { + return uuid + ' ' + fullName; + } + + @Override + public int compareTo(Resume o) { + int compareFullName = fullName.compareTo(o.fullName); + return compareFullName == 0 ? uuid.compareTo(o.uuid) : compareFullName; + } +} \ No newline at end of file diff --git a/src/com/learnjava/model/Section.java b/src/com/learnjava/model/Section.java new file mode 100644 index 0000000..2323371 --- /dev/null +++ b/src/com/learnjava/model/Section.java @@ -0,0 +1,6 @@ +package com.learnjava.model; + +import java.io.Serializable; + +abstract public class Section implements Serializable { +} diff --git a/src/com/learnjava/model/SectionType.java b/src/com/learnjava/model/SectionType.java new file mode 100644 index 0000000..3aa7a85 --- /dev/null +++ b/src/com/learnjava/model/SectionType.java @@ -0,0 +1,20 @@ +package com.learnjava.model; + +public enum SectionType { + OBJECTIVE("Позиция"), + PERSONAL("Личные качества"), + ACHIEVEMENT("Достижения"), + QUALIFICATIONS("Квалификация"), + EXPERIENCE("Опыт работы"), + EDUCATION("Образование"); + + private String title; + + SectionType(String title) { + this.title = title; + } + + public String getTitle() { + return title; + } +} diff --git a/src/com/learnjava/model/TextSection.java b/src/com/learnjava/model/TextSection.java new file mode 100644 index 0000000..8143377 --- /dev/null +++ b/src/com/learnjava/model/TextSection.java @@ -0,0 +1,36 @@ +package com.learnjava.model; + +public class TextSection extends Section { + private String content; + + public TextSection(String content) { + this.content = content; + } + @SuppressWarnings("unused") + public TextSection() { + } + public String getContent() { + return content; + } + + @Override + public String toString() { + return content; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + TextSection that = (TextSection) o; + + return content.equals(that.content); + + } + + @Override + public int hashCode() { + return content.hashCode(); + } +} \ No newline at end of file diff --git a/src/com/learnjava/sql/ConnectionFactory.java b/src/com/learnjava/sql/ConnectionFactory.java new file mode 100644 index 0000000..9afd543 --- /dev/null +++ b/src/com/learnjava/sql/ConnectionFactory.java @@ -0,0 +1,8 @@ +package com.learnjava.sql; + +import java.sql.Connection; +import java.sql.SQLException; + +public interface ConnectionFactory { + Connection getConnection() throws SQLException; +} \ No newline at end of file diff --git a/src/com/learnjava/sql/ExceptionUtil.java b/src/com/learnjava/sql/ExceptionUtil.java new file mode 100644 index 0000000..b433826 --- /dev/null +++ b/src/com/learnjava/sql/ExceptionUtil.java @@ -0,0 +1,22 @@ +package com.learnjava.sql; + +import org.postgresql.util.PSQLException; +import com.learnjava.exception.ExistStorageException; +import com.learnjava.exception.StorageException; + +import java.sql.SQLException; + +public class ExceptionUtil { + private ExceptionUtil() { + } + + public static StorageException convertException(SQLException e) { + if (e instanceof PSQLException) { + + if (e.getSQLState().equals("23505")) { + return new ExistStorageException(null); + } + } + return new StorageException(e); + } +} \ No newline at end of file diff --git a/src/com/learnjava/sql/SqlExecutor.java b/src/com/learnjava/sql/SqlExecutor.java new file mode 100644 index 0000000..c1c56a8 --- /dev/null +++ b/src/com/learnjava/sql/SqlExecutor.java @@ -0,0 +1,9 @@ +package com.learnjava.sql; + + +import java.sql.PreparedStatement; +import java.sql.SQLException; + +public interface SqlExecutor { + T execute(PreparedStatement st) throws SQLException; +} \ No newline at end of file diff --git a/src/com/learnjava/sql/SqlHelper.java b/src/com/learnjava/sql/SqlHelper.java new file mode 100644 index 0000000..e8d5492 --- /dev/null +++ b/src/com/learnjava/sql/SqlHelper.java @@ -0,0 +1,26 @@ +package com.learnjava.sql; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.SQLException; + +public class SqlHelper { + private final ConnectionFactory connectionFactory; + + public SqlHelper(ConnectionFactory connectionFactory) { + this.connectionFactory = connectionFactory; + } + + public void execute(String sql) { + execute(sql, PreparedStatement::execute); + } + + public T execute(String sql, SqlExecutor executor) { + try (Connection conn = connectionFactory.getConnection(); + PreparedStatement ps = conn.prepareStatement(sql)) { + return executor.execute(ps); + } catch (SQLException e) { + throw ExceptionUtil.convertException(e); + } + } +} \ No newline at end of file diff --git a/src/com/learnjava/storage/AbstractArrayStorage.java b/src/com/learnjava/storage/AbstractArrayStorage.java new file mode 100644 index 0000000..32a92a1 --- /dev/null +++ b/src/com/learnjava/storage/AbstractArrayStorage.java @@ -0,0 +1,71 @@ +package com.learnjava.storage; + +import com.learnjava.exception.StorageException; +import com.learnjava.model.Resume; + +import java.util.Arrays; +import java.util.List; + + +/** + * Array based storage for Resumes + */ +public abstract class AbstractArrayStorage extends AbstractStorage { + protected static final int STORAGE_LIMIT = 10000; + + protected Resume[] storage = new Resume[STORAGE_LIMIT]; + protected int size = 0; + + public int size() { + return size; + } + + public void clear() { + Arrays.fill(storage, 0, size, null); + size = 0; + } + + @Override + protected void doUpdate(Resume r, Object index) { + storage[(Integer) index] = r; + } + + + public List doCopyAll() { + return Arrays.asList(Arrays.copyOfRange(storage, 0, size)); + } + + @Override + protected void doSave(Resume r, Object index) { + if (size == STORAGE_LIMIT) { + throw new StorageException("Storage overflow", r.getUuid()); + } else { + insertElement(r, (Integer) index); + size++; + } + } + + @Override + public void doDelete(Object index) { + fillDeletedElement((Integer) index); + storage[size - 1] = null; + size--; + } + + public Resume doGet(Object index) { + return storage[(Integer) index]; + } + + + @Override + protected boolean isExist(Object index) { + return (Integer) index >= 0; + } + + + protected abstract void fillDeletedElement(int index); + + protected abstract void insertElement(Resume r, int index); + + protected abstract Integer getSearchKey(String uuid); +} \ No newline at end of file diff --git a/src/com/learnjava/storage/AbstractStorage.java b/src/com/learnjava/storage/AbstractStorage.java new file mode 100644 index 0000000..2a49ce7 --- /dev/null +++ b/src/com/learnjava/storage/AbstractStorage.java @@ -0,0 +1,66 @@ +package com.learnjava.storage; + +import com.learnjava.exception.ExistStorageException; +import com.learnjava.exception.NotExistStorageException; +import com.learnjava.model.Resume; + +import java.util.Comparator; +import java.util.List; + +public abstract class AbstractStorage implements Storage { + protected abstract SK getSearchKey(String uuid); + + protected abstract void doUpdate(Resume r, SK searchKey); + + protected abstract boolean isExist(SK searchKey); + + protected abstract void doSave(Resume r, SK searchKey); + + protected abstract Resume doGet(SK searchKey); + + protected abstract void doDelete(SK searchKey); + + protected abstract List doCopyAll(); + private static final Comparator RESUME_COMPARATOR = Comparator.comparing(Resume::getUuid); + public void update(Resume r) { + SK searchKey = getExistedSearchKey(r.getUuid()); + doUpdate(r, searchKey); + } + + public void save(Resume r) { + SK searchKey = getNotExistedSearchKey(r.getUuid()); + doSave(r, searchKey); + } + + public void delete(String uuid) { + SK searchKey = getExistedSearchKey(uuid); + doDelete(searchKey); + } + + public Resume get(String uuid) { + SK searchKey = getExistedSearchKey(uuid); + return doGet(searchKey); + } + + private SK getExistedSearchKey(String uuid) { + SK searchKey = getSearchKey(uuid); + if (!isExist(searchKey)) { + throw new NotExistStorageException(uuid); + } + return searchKey; + } + + private SK getNotExistedSearchKey(String uuid) { + SK searchKey = getSearchKey(uuid); + if (isExist(searchKey)) { + throw new ExistStorageException(uuid); + } + return searchKey; + } + @Override + public List getAllSorted() { + List list = doCopyAll(); + list.sort(RESUME_COMPARATOR); + return list; + } +} diff --git a/src/com/learnjava/storage/ArrayStorage.java b/src/com/learnjava/storage/ArrayStorage.java new file mode 100644 index 0000000..324c854 --- /dev/null +++ b/src/com/learnjava/storage/ArrayStorage.java @@ -0,0 +1,27 @@ +package com.learnjava.storage; +import com.learnjava.model.Resume; +/** + * Array based storage for Resumes + */ +public class ArrayStorage extends AbstractArrayStorage { + + @Override + protected void fillDeletedElement(int index) { + storage[index] = storage[size - 1]; + } + + @Override + protected void insertElement(Resume r, int index) { + storage[size] = r; + } + + protected Integer getSearchKey(String uuid) { + for (int i = 0; i < size; i++) { + if (uuid.equals(storage[i].getUuid())) { + return i; + } + } + return -1; + } + +} \ No newline at end of file diff --git a/src/com/learnjava/storage/FileStorage.java b/src/com/learnjava/storage/FileStorage.java new file mode 100644 index 0000000..a4c787c --- /dev/null +++ b/src/com/learnjava/storage/FileStorage.java @@ -0,0 +1,108 @@ +package com.learnjava.storage; + +import com.learnjava.exception.StorageException; +import com.learnjava.model.Resume; +import com.learnjava.storage.serializer.StreamSerializer; + +import java.io.*; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +public class FileStorage extends AbstractStorage { + private final File directory; + private final StreamSerializer streamSerializer; + + protected FileStorage(File directory, StreamSerializer streamSerializer) { + Objects.requireNonNull(directory, "directory must not be null"); + + this.streamSerializer = streamSerializer; + if (!directory.isDirectory()) { + throw new IllegalArgumentException(directory.getAbsolutePath() + " is not directory"); + } + if (!directory.canRead() || !directory.canWrite()) { + throw new IllegalArgumentException(directory.getAbsolutePath() + " is not readable/writable"); + } + this.directory = directory; + } + + @Override + public void clear() { + File[] files = doGetFiles(); + if (files != null) { + for (File file : files) { + doDelete(file); + } + } + } + + @Override + public int size() { + return doGetFiles().length; + } + + @Override + protected File getSearchKey(String uuid) { + return new File(directory, uuid); + } + + @Override + protected void doUpdate(Resume r, File file) { + try { + streamSerializer.doWrite(r, new BufferedOutputStream(new FileOutputStream(file))); + } catch (IOException e) { + throw new StorageException("File write error", r.getUuid(), e); + } + } + + @Override + protected boolean isExist(File file) { + return file.exists(); + } + + @Override + protected void doSave(Resume r, File file) { + try { + //noinspection ResultOfMethodCallIgnored + file.createNewFile(); + } catch (IOException e) { + throw new StorageException("Couldn't create file " + file.getAbsolutePath(), file.getName(), e); + } + doUpdate(r, file); + } + + @Override + protected Resume doGet(File file) { + try { + return streamSerializer.doRead(new BufferedInputStream(new FileInputStream(file))); + } catch (IOException e) { + throw new StorageException("File read error", file.getName(), e); + } + } + + @Override + protected void doDelete(File file) { + if (!file.delete()) { + throw new StorageException("File delete error", file.getName()); + } + } + + @Override + protected List doCopyAll() { + File[] files = doGetFiles(); + List list = new ArrayList<>(files.length); + for (File file : files) { + list.add(doGet(file)); + } + return list; + } + + protected File[] doGetFiles() { + File[] files = directory.listFiles(); + if (files == null) { + throw new StorageException("Directory read error"); + } else { + return files; + } + } +} \ No newline at end of file diff --git a/src/com/learnjava/storage/ListStorage.java b/src/com/learnjava/storage/ListStorage.java new file mode 100644 index 0000000..c600304 --- /dev/null +++ b/src/com/learnjava/storage/ListStorage.java @@ -0,0 +1,61 @@ +package com.learnjava.storage; + +import com.learnjava.model.Resume; + +import java.util.ArrayList; +import java.util.List; + +public class ListStorage extends AbstractStorage { + protected List list = new ArrayList<>(); + + @Override + protected Integer getSearchKey(String uuid) { + for (int i = 0; i < list.size(); i++) { + if (list.get(i).getUuid().equals(uuid)) { + return i; + } + } + return null; + } + + @Override + protected boolean isExist(Object searchKey) { + return searchKey != null; + } + + @Override + protected void doUpdate(Resume r, Object searchKey) { + list.set((Integer) searchKey, r); + } + + @Override + protected void doSave(Resume r, Object searchKey) { + list.add(r); + } + + @Override + protected Resume doGet(Object searchKey) { + return list.get((Integer) searchKey); + } + + @Override + protected void doDelete(Object searchKey) { + list.remove(((Integer) searchKey).intValue()); + } + + @Override + public void clear() { + list.clear(); + } + + @Override + public List doCopyAll() { + return new ArrayList<>(list); + } + + @Override + public int size() { + return list.size(); + } + +} diff --git a/src/com/learnjava/storage/MapStorage.java b/src/com/learnjava/storage/MapStorage.java new file mode 100644 index 0000000..89d27d8 --- /dev/null +++ b/src/com/learnjava/storage/MapStorage.java @@ -0,0 +1,57 @@ +package com.learnjava.storage; + +import com.learnjava.model.Resume; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.ArrayList; + +public class MapStorage extends AbstractStorage { + private final Map map = new HashMap<>(); + + @Override + protected Resume getSearchKey(String uuid) { + return map.get(uuid); + } + + @Override + protected void doUpdate(Resume r, Object searchKey) { + map.put(r.getUuid(), r); + } + + @Override + protected boolean isExist(Object searchKey) { + return searchKey != null; + } + + @Override + protected void doSave(Resume r, Object searchKey) { + map.put(r.getUuid(), r); + } + + @Override + protected Resume doGet(Object searchKey) { + return (Resume) searchKey; + } + + @Override + protected void doDelete(Object searchKey) { + map.remove(((Resume) searchKey).getUuid()); + } + + @Override + public void clear() { + map.clear(); + } + + @Override + public List doCopyAll() { + return new ArrayList<>(map.values()); + } + + @Override + public int size() { + return map.size(); + } +} diff --git a/src/com/learnjava/storage/MapUUIDStorage.java b/src/com/learnjava/storage/MapUUIDStorage.java new file mode 100644 index 0000000..93f99d1 --- /dev/null +++ b/src/com/learnjava/storage/MapUUIDStorage.java @@ -0,0 +1,56 @@ +package com.learnjava.storage; + +import com.learnjava.model.Resume; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class MapUUIDStorage extends AbstractStorage { + private final Map map = new HashMap<>(); + + @Override + protected String getSearchKey(String uuid) { + return uuid; + } + + @Override + protected void doUpdate(Resume r, Object uuid) { + map.put((String) uuid, r); + } + @Override + protected boolean isExist(Object uuid) { + return map.containsKey((String) uuid); + } + + @Override + protected void doSave(Resume r, Object uuid) { + map.put((String) uuid, r); + } + + @Override + protected Resume doGet(Object uuid) { + return map.get((String) uuid); + } + + @Override + protected void doDelete(Object uuid) { + map.remove((String) uuid); + } + + @Override + public void clear() { + map.clear(); + } + + + @Override + public List doCopyAll() { + return new ArrayList<>(map.values()); + } + @Override + public int size() { + return map.size(); + } +} diff --git a/src/com/learnjava/storage/PathStorage.java b/src/com/learnjava/storage/PathStorage.java new file mode 100644 index 0000000..5eba898 --- /dev/null +++ b/src/com/learnjava/storage/PathStorage.java @@ -0,0 +1,105 @@ +package com.learnjava.storage; + +import com.learnjava.exception.StorageException; +import com.learnjava.model.Resume; +import com.learnjava.storage.serializer.StreamSerializer; + +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; +import java.io.IOException; +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; +import java.util.stream.Stream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; + +public class PathStorage extends AbstractStorage { + private final Path directory; + private final StreamSerializer streamSerializer; + + protected PathStorage(String dir, StreamSerializer streamSerializer) { + Objects.requireNonNull(dir, "directory must not be null"); + + this.streamSerializer = streamSerializer; + directory = Paths.get(dir); + if (!Files.isDirectory(directory) || !Files.isWritable(directory)) { + throw new IllegalArgumentException(dir + " is not directory or is not writable"); + } + } + + @Override + public void clear() { + getFilesList().forEach(this::doDelete); + } + + @Override + public int size() { + return (int) getFilesList().count(); + } + + @Override + protected Path getSearchKey(String uuid) { + return directory.resolve(uuid); + } + + @Override + protected void doUpdate(Resume r, Path path) { + try { + streamSerializer.doWrite(r, new BufferedOutputStream(Files.newOutputStream(path))); + } catch (IOException e) { + throw new StorageException("Path write error", r.getUuid(), e); + } + } + + @Override + protected boolean isExist(Path path) { + return Files.isRegularFile(path); + } + + @Override + protected void doSave(Resume r, Path path) { + try { + Files.createFile(path); + } catch (IOException e) { + throw new StorageException("Couldn't create path " + path, getFileName(path), e); + } + doUpdate(r, path); + } + + @Override + protected Resume doGet(Path path) { + try { + return streamSerializer.doRead(new BufferedInputStream(Files.newInputStream(path))); + } catch (IOException e) { + throw new StorageException("Path read error", getFileName(path), e); + } + } + + @Override + protected void doDelete(Path path) { + try { + Files.delete(path); + } catch (IOException e) { + throw new StorageException("Path delete error", getFileName(path), e); + } + } + + @Override + protected List doCopyAll() { + return getFilesList().map(this::doGet).collect(Collectors.toList()); + } + + private String getFileName(Path path) { + return path.getFileName().toString(); + } + + private Stream getFilesList() { + try { + return Files.list(directory); + } catch (IOException e) { + throw new StorageException("Directory read error", e); + } + } +} \ No newline at end of file diff --git a/src/com/learnjava/storage/SortedArrayStorage.java b/src/com/learnjava/storage/SortedArrayStorage.java new file mode 100644 index 0000000..d3ce380 --- /dev/null +++ b/src/com/learnjava/storage/SortedArrayStorage.java @@ -0,0 +1,33 @@ +package com.learnjava.storage; + +import com.learnjava.model.Resume; + +import java.util.Arrays; +import java.util.Comparator; + + +public class SortedArrayStorage extends AbstractArrayStorage { + + private static final Comparator RESUME_COMPARATOR = Comparator.comparing(Resume::getUuid); + @Override + protected void fillDeletedElement(int index) { + int numMoved = size - index - 1; + if (numMoved > 0) { + System.arraycopy(storage, index + 1, storage, index, numMoved); + } + } + + @Override + protected void insertElement(Resume r, int index) { +// http://codereview.stackexchange.com/questions/36221/binary-search-for-inserting-in-array#answer-36239 + int insertIdx = -index - 1; + System.arraycopy(storage, insertIdx, storage, insertIdx + 1, size - insertIdx); + storage[insertIdx] = r; + } + + @Override + protected Integer getSearchKey(String uuid) { + Resume searchKey = new Resume(uuid, "dummy"); + return Arrays.binarySearch(storage, 0, size, searchKey, RESUME_COMPARATOR); + } +} \ No newline at end of file diff --git a/src/com/learnjava/storage/SqlStorage.java b/src/com/learnjava/storage/SqlStorage.java new file mode 100644 index 0000000..9037e50 --- /dev/null +++ b/src/com/learnjava/storage/SqlStorage.java @@ -0,0 +1,87 @@ +package com.learnjava.storage; + +import com.learnjava.exception.NotExistStorageException; +import com.learnjava.model.Resume; +import com.learnjava.sql.SqlHelper; +import java.sql.DriverManager; +import java.sql.ResultSet; +import java.util.ArrayList; +import java.util.List; + +public class SqlStorage implements Storage { + public final SqlHelper sqlHelper; + + public SqlStorage(String dbUrl, String dbUser, String dbPassword) { + sqlHelper = new SqlHelper(() -> DriverManager.getConnection(dbUrl, dbUser, dbPassword)); + } + + @Override + public void clear() { + sqlHelper.execute("DELETE FROM resume"); + } + + @Override + public Resume get(String uuid) { + return sqlHelper.execute("SELECT * FROM resume r WHERE r.uuid =?", ps -> { + ps.setString(1, uuid); + ResultSet rs = ps.executeQuery(); + if (!rs.next()) { + throw new NotExistStorageException(uuid); + } + return new Resume(uuid, rs.getString("full_name")); + }); + } + + @Override + public void update(Resume r) { + sqlHelper.execute("UPDATE resume SET full_name = ? WHERE uuid = ?", ps -> { + ps.setString(1, r.getFullName()); + ps.setString(2, r.getUuid()); + if (ps.executeUpdate() == 0) { + throw new NotExistStorageException(r.getUuid()); + } + return null; + }); + } + + @Override + public void save(Resume r) { + sqlHelper.execute("INSERT INTO resume (uuid, full_name) VALUES (?,?)", ps -> { + ps.setString(1, r.getUuid()); + ps.setString(2, r.getFullName()); + ps.execute(); + return null; + }); + } + + @Override + public void delete(String uuid) { + sqlHelper.execute("DELETE FROM resume WHERE uuid=?", ps -> { + ps.setString(1, uuid); + if (ps.executeUpdate() == 0) { + throw new NotExistStorageException(uuid); + } + return null; + }); + } + + @Override + public List getAllSorted() { + return sqlHelper.execute("SELECT * FROM resume r ORDER BY full_name,uuid", ps -> { + ResultSet rs = ps.executeQuery(); + List resumes = new ArrayList<>(); + while (rs.next()) { + resumes.add(new Resume(rs.getString("uuid"), rs.getString("full_name"))); + } + return resumes; + }); + } + + @Override + public int size() { + return sqlHelper.execute("SELECT count(*) FROM resume", st -> { + ResultSet rs = st.executeQuery(); + return rs.next() ? rs.getInt(1) : 0; + }); + } +} \ No newline at end of file diff --git a/src/com/learnjava/storage/Storage.java b/src/com/learnjava/storage/Storage.java new file mode 100644 index 0000000..e77472e --- /dev/null +++ b/src/com/learnjava/storage/Storage.java @@ -0,0 +1,22 @@ +package com.learnjava.storage; + +import com.learnjava.model.Resume; + +import java.util.List; + +public interface Storage { + + void clear(); + + void update(Resume r); + + void save(Resume r); + + Resume get(String uuid); + + void delete(String uuid); + + List getAllSorted(); + + int size(); +} \ No newline at end of file diff --git a/src/com/learnjava/storage/serializer/DataStreamSerializer.java b/src/com/learnjava/storage/serializer/DataStreamSerializer.java new file mode 100644 index 0000000..589e45c --- /dev/null +++ b/src/com/learnjava/storage/serializer/DataStreamSerializer.java @@ -0,0 +1,116 @@ +package com.learnjava.storage.serializer; + +import com.learnjava.model.*; + +import java.io.*; +import java.util.List; +import java.util.Map; +import java.util.ArrayList; +import java.util.Collection; +import java.time.LocalDate; + +public class DataStreamSerializer implements StreamSerializer { + + @Override + public void doWrite(Resume r, OutputStream os) throws IOException { + try (DataOutputStream dos = new DataOutputStream(os)) { + dos.writeUTF(r.getUuid()); + dos.writeUTF(r.getFullName()); + Map contacts = r.getContacts(); + writeCollection(dos, contacts.entrySet(), entry -> { + dos.writeUTF(entry.getKey().name()); + dos.writeUTF(entry.getValue()); + }); + + writeCollection(dos, r.getSections().entrySet(), entry -> { + SectionType type = entry.getKey(); + Section section = entry.getValue(); + dos.writeUTF(type.name()); + switch (type) { + case PERSONAL, OBJECTIVE -> dos.writeUTF(((TextSection) section).getContent()); + case ACHIEVEMENT, QUALIFICATIONS -> + writeCollection(dos, ((ListSection) section).getItems(), dos::writeUTF); + case EXPERIENCE, EDUCATION -> + writeCollection(dos, ((OrganizationSection) section).getOrganizations(), org -> { + dos.writeUTF(org.getHomePage().getName()); + dos.writeUTF(org.getHomePage().getUrl()); + writeCollection(dos, org.getPositions(), position -> { + writeLocalDate(dos, position.getStartDate()); + writeLocalDate(dos, position.getEndDate()); + dos.writeUTF(position.getTitle()); + dos.writeUTF(position.getDescription()); + }); + }); + } + }); + } + } + + private void writeLocalDate(DataOutputStream dos, LocalDate ld) throws IOException { + dos.writeInt(ld.getYear()); + dos.writeInt(ld.getMonth().getValue()); + } + + private LocalDate readLocalDate(DataInputStream dis) throws IOException { + return LocalDate.of(dis.readInt(), dis.readInt(), 1); + } + + @Override + public Resume doRead(InputStream is) throws IOException { + try (DataInputStream dis = new DataInputStream(is)) { + String uuid = dis.readUTF(); + String fullName = dis.readUTF(); + Resume resume = new Resume(uuid, fullName); + readItems(dis, () -> resume.addContact(ContactType.valueOf(dis.readUTF()), dis.readUTF())); + readItems(dis, () -> { + SectionType sectionType = SectionType.valueOf(dis.readUTF()); + resume.addSection(sectionType, readSection(dis, sectionType)); + }); + return resume; + } + } + + private Section readSection(DataInputStream dis, SectionType sectionType) throws IOException { + return switch (sectionType) { + case PERSONAL, OBJECTIVE -> new TextSection(dis.readUTF()); + case ACHIEVEMENT, QUALIFICATIONS -> new ListSection(readList(dis, dis::readUTF)); + case EXPERIENCE, EDUCATION -> + new OrganizationSection(readList(dis, () -> new Organization(dis.readUTF(), dis.readUTF(), readList(dis, () -> new Period(readLocalDate(dis), readLocalDate(dis), dis.readUTF(), dis.readUTF()))))); + }; + } + + private List readList(DataInputStream dis, ElementReader reader) throws IOException { + int size = dis.readInt(); + List list = new ArrayList<>(size); + for (int i = 0; i < size; i++) { + list.add(reader.read()); + } + return list; + } + + private void readItems(DataInputStream dis, ElementProcessor processor) throws IOException { + int size = dis.readInt(); + for (int i = 0; i < size; i++) { + processor.process(); + } + } + + private void writeCollection(DataOutputStream dos, Collection collection, ElementWriter writer) throws IOException { + dos.writeInt(collection.size()); + for (T item : collection) { + writer.write(item); + } + } + + private interface ElementProcessor { + void process() throws IOException; + } + + private interface ElementReader { + T read() throws IOException; + } + + private interface ElementWriter { + void write(T t) throws IOException; + } +} \ No newline at end of file diff --git a/src/com/learnjava/storage/serializer/JsonStreamSerializer.java b/src/com/learnjava/storage/serializer/JsonStreamSerializer.java new file mode 100644 index 0000000..f74d0b6 --- /dev/null +++ b/src/com/learnjava/storage/serializer/JsonStreamSerializer.java @@ -0,0 +1,23 @@ +package com.learnjava.storage.serializer; + +import com.learnjava.model.Resume; +import com.learnjava.util.JsonParser; + +import java.io.*; +import java.nio.charset.StandardCharsets; +public class JsonStreamSerializer implements StreamSerializer { + + @Override + public void doWrite(Resume r, OutputStream os) throws IOException { + try (Writer writer = new OutputStreamWriter(os, StandardCharsets.UTF_8)) { + JsonParser.write(r, writer); + } + } + + @Override + public Resume doRead(InputStream is) throws IOException { + try (Reader reader = new InputStreamReader(is, StandardCharsets.UTF_8)) { + return JsonParser.read(reader, Resume.class); + } + } +} \ No newline at end of file diff --git a/src/com/learnjava/storage/serializer/ObjectStreamSerializer.java b/src/com/learnjava/storage/serializer/ObjectStreamSerializer.java new file mode 100644 index 0000000..af0ff8a --- /dev/null +++ b/src/com/learnjava/storage/serializer/ObjectStreamSerializer.java @@ -0,0 +1,24 @@ +package com.learnjava.storage.serializer; + +import com.learnjava.exception.StorageException; +import com.learnjava.model.Resume; + +import java.io.*; +public class ObjectStreamSerializer implements StreamSerializer { + + @Override + public void doWrite(Resume r, OutputStream os) throws IOException { + try (ObjectOutputStream oos = new ObjectOutputStream(os)) { + oos.writeObject(r); + } + } + + @Override + public Resume doRead(InputStream is) throws IOException { + try (ObjectInputStream ois = new ObjectInputStream(is)) { + return (Resume) ois.readObject(); + } catch (ClassNotFoundException e) { + throw new StorageException("Error read resume", null, e); + } + } +} diff --git a/src/com/learnjava/storage/serializer/StreamSerializer.java b/src/com/learnjava/storage/serializer/StreamSerializer.java new file mode 100644 index 0000000..f89c5be --- /dev/null +++ b/src/com/learnjava/storage/serializer/StreamSerializer.java @@ -0,0 +1,12 @@ +package com.learnjava.storage.serializer; + +import com.learnjava.model.Resume; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +public interface StreamSerializer { + void doWrite(Resume r, OutputStream os) throws IOException; + + Resume doRead(InputStream is) throws IOException; +} \ No newline at end of file diff --git a/src/com/learnjava/storage/serializer/XmlStreamSerializer.java b/src/com/learnjava/storage/serializer/XmlStreamSerializer.java new file mode 100644 index 0000000..0d89aba --- /dev/null +++ b/src/com/learnjava/storage/serializer/XmlStreamSerializer.java @@ -0,0 +1,36 @@ +package com.learnjava.storage.serializer; + +import com.learnjava.util.XmlParser; +import com.learnjava.model.*; + +import java.io.*; +import java.nio.charset.StandardCharsets; + +public class XmlStreamSerializer implements StreamSerializer { + private final XmlParser xmlParser; + + public XmlStreamSerializer() { + xmlParser = new XmlParser( + Resume.class, + Organization.class, + Link.class, + OrganizationSection.class, + TextSection.class, + ListSection.class, + Period.class); + } + + @Override + public void doWrite(Resume r, OutputStream os) throws IOException { + try (Writer w = new OutputStreamWriter(os, StandardCharsets.UTF_8)) { + xmlParser.marshall(r, w); + } + } + + @Override + public Resume doRead(InputStream is) throws IOException { + try (Reader r = new InputStreamReader(is, StandardCharsets.UTF_8)) { + return xmlParser.unmarshall(r); + } + } +} \ No newline at end of file diff --git a/src/com/learnjava/util/JsonParser.java b/src/com/learnjava/util/JsonParser.java new file mode 100644 index 0000000..868c70b --- /dev/null +++ b/src/com/learnjava/util/JsonParser.java @@ -0,0 +1,26 @@ +package com.learnjava.util; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.learnjava.model.Section; + +import java.io.Reader; +import java.io.Writer; +import java.time.LocalDate; + +public class JsonParser { + + private final static Gson GSON = new GsonBuilder() + .registerTypeAdapter(Section.class, new JsonSectionAdapter()) + .registerTypeAdapter(LocalDate.class, new LocalDateTypeAdapter()) + .create(); + + public static T read(Reader reader, Class clazz) { + return GSON.fromJson(reader, clazz); + } + + public static void write(T object, Writer writer) { + GSON.toJson(object, writer); + } + +} \ No newline at end of file diff --git a/src/com/learnjava/util/JsonSectionAdapter.java b/src/com/learnjava/util/JsonSectionAdapter.java new file mode 100644 index 0000000..db89ee7 --- /dev/null +++ b/src/com/learnjava/util/JsonSectionAdapter.java @@ -0,0 +1,33 @@ +package com.learnjava.util; + +import com.google.gson.*; + +import java.lang.reflect.Type; + +public class JsonSectionAdapter implements JsonSerializer, JsonDeserializer { + private static final String CLASSNAME = "CLASSNAME"; + private static final String INSTANCE = "INSTANCE"; + + @Override + public T deserialize(JsonElement json, Type type, JsonDeserializationContext context) throws JsonParseException { + JsonObject jsonObject = json.getAsJsonObject(); + JsonPrimitive prim = (JsonPrimitive) jsonObject.get(CLASSNAME); + String className = prim.getAsString(); + + try { + Class clazz = Class.forName(className); + return context.deserialize(jsonObject.get(INSTANCE), clazz); + } catch (ClassNotFoundException e) { + throw new JsonParseException(e.getMessage()); + } + } + + @Override + public JsonElement serialize(T section, Type type, JsonSerializationContext context) { + JsonObject retValue = new JsonObject(); + retValue.addProperty(CLASSNAME, section.getClass().getName()); + JsonElement elem = context.serialize(section); + retValue.add(INSTANCE, elem); + return retValue; + } +} diff --git a/src/com/learnjava/util/LocalDateAdapter.java b/src/com/learnjava/util/LocalDateAdapter.java new file mode 100644 index 0000000..9430bfc --- /dev/null +++ b/src/com/learnjava/util/LocalDateAdapter.java @@ -0,0 +1,18 @@ +package com.learnjava.util; + +import javax.xml.bind.annotation.adapters.XmlAdapter; +import java.time.LocalDate; + +public class LocalDateAdapter extends XmlAdapter { + + @SuppressWarnings("RedundantThrows") + @Override + public LocalDate unmarshal(String str) throws Exception { + return LocalDate.parse(str); + } + @SuppressWarnings("RedundantThrows") + @Override + public String marshal(LocalDate ld) throws Exception { + return ld.toString(); + } +} \ No newline at end of file diff --git a/src/com/learnjava/util/LocalDateTypeAdapter.java b/src/com/learnjava/util/LocalDateTypeAdapter.java new file mode 100644 index 0000000..997b5f7 --- /dev/null +++ b/src/com/learnjava/util/LocalDateTypeAdapter.java @@ -0,0 +1,24 @@ +package com.learnjava.util; + +import com.google.gson.*; + +import java.lang.reflect.Type; +import java.time.LocalDate; +import java.time.format.DateTimeFormatter; + +public class LocalDateTypeAdapter implements JsonSerializer, JsonDeserializer { + + private final DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd"); + + @Override + public JsonElement serialize(final LocalDate date, final Type typeOfSrc, + final JsonSerializationContext context) { + return new JsonPrimitive(date.format(formatter)); + } + + @Override + public LocalDate deserialize(final JsonElement json, final Type typeOfT, + final JsonDeserializationContext context) throws JsonParseException { + return LocalDate.parse(json.getAsString(), formatter); + } +} diff --git a/src/com/learnjava/util/XmlParser.java b/src/com/learnjava/util/XmlParser.java new file mode 100644 index 0000000..870138f --- /dev/null +++ b/src/com/learnjava/util/XmlParser.java @@ -0,0 +1,43 @@ +package com.learnjava.util; + +import javax.xml.bind.JAXBContext; +import javax.xml.bind.JAXBException; +import javax.xml.bind.Marshaller; +import javax.xml.bind.Unmarshaller; +import java.io.Reader; +import java.io.Writer; + +public class XmlParser { + private final Marshaller marshaller; + private final Unmarshaller unmarshaller; + + public XmlParser(Class... classesToBeBound) { + try { + JAXBContext ctx = JAXBContext.newInstance(classesToBeBound); + + marshaller = ctx.createMarshaller(); + marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true); + marshaller.setProperty(Marshaller.JAXB_ENCODING, "UTF-8"); + + unmarshaller = ctx.createUnmarshaller(); + } catch (JAXBException e) { + throw new IllegalStateException(e); + } + } + + public T unmarshall(Reader reader) { + try { + return (T) unmarshaller.unmarshal(reader); + } catch (JAXBException e) { + throw new IllegalStateException(e); + } + } + + public void marshall(Object instance, Writer writer) { + try { + marshaller.marshal(instance, writer); + } catch (JAXBException e) { + throw new IllegalStateException(e); + } + } +} diff --git a/test/com/learnjava/storage/AbstractArrayStorageTest.java b/test/com/learnjava/storage/AbstractArrayStorageTest.java new file mode 100644 index 0000000..709a9ab --- /dev/null +++ b/test/com/learnjava/storage/AbstractArrayStorageTest.java @@ -0,0 +1,22 @@ +package com.learnjava.storage; + +import com.learnjava.exception.StorageException; +import com.learnjava.model.Resume; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +public abstract class AbstractArrayStorageTest extends AbstractStorageTest { + protected AbstractArrayStorageTest(Storage storage, Storage emptyStorage) { + super(storage, emptyStorage); + } + @Test + void overflow() { + storage.clear(); + for (int i = 0; i < AbstractArrayStorage.STORAGE_LIMIT; i++) { + storage.save(new Resume("dummy" + i )); + } + Assertions.assertThrows(StorageException.class, () -> storage.save(new Resume("full dummy"))); + + } +} + diff --git a/test/com/learnjava/storage/AbstractStorageTest.java b/test/com/learnjava/storage/AbstractStorageTest.java new file mode 100644 index 0000000..2f6028c --- /dev/null +++ b/test/com/learnjava/storage/AbstractStorageTest.java @@ -0,0 +1,134 @@ +package com.learnjava.storage; + +import com.learnjava.Config; +import com.learnjava.exception.NotExistStorageException; +import com.learnjava.model.Resume; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.io.File; +import java.util.*; + +import static com.learnjava.ResumeTestData.getFilledResume; + +public abstract class AbstractStorageTest { + protected static final File STORAGE_DIR = Config.get().getStorageDir(); + protected static final File STORAGE_DIR_EMPTY = Config.get().getStorageDirEmpty(); + private static final List sortedUuidList = generateSortedUuid(5); + private static final String UUID_1 = readSortedUuidList(0); + private static final Resume RESUME_1 = getFilledResume(UUID_1, "Ivanov"); + private static final String UUID_2 = readSortedUuidList(1); + private static final Resume RESUME_2 = getFilledResume(UUID_2, "Petrov"); + private static final String UUID_3 = readSortedUuidList(2); + private static final Resume RESUME_3 = getFilledResume(UUID_3, "Sidorov"); + private static final String UUID_4 = readSortedUuidList(3); + private static final Resume RESUME_4 = getFilledResume(UUID_4, "Kuznetcov"); + private static final String UUID_NOT_EXIST = readSortedUuidList(4); + private static final Resume RESUME_5 = getFilledResume(UUID_NOT_EXIST, "Gzhegosh"); + + protected Storage storage; + protected Storage emptyStorage; + + protected AbstractStorageTest(Storage storage, Storage emptyStorage) { + this.storage = storage; + this.emptyStorage = emptyStorage; + } + + protected AbstractStorageTest(Storage storage) { + this.storage = storage; + } + + private static List generateSortedUuid(int quan) { + List sortedList = new ArrayList<>(); + for (int i = 0; i < quan; i++) { + sortedList.add(UUID.randomUUID().toString()); + } + Collections.sort(sortedList); + return sortedList; + } + + private static String readSortedUuidList(int index) { + return sortedUuidList.get(index); + } + + @BeforeEach + public void beforeEach() { + if (storage != null) { + storage.clear(); + storage.save(RESUME_1); + storage.save(RESUME_2); + storage.save(RESUME_3); + } + } + + private void assertGet(Resume r) { + Assertions.assertEquals(r, storage.get(r.getUuid())); + } + + private void assertSize(int size) { + Assertions.assertEquals(size, storage.size()); + } + + @Test + void size() { + assertSize(3); + } + + @Test + void clear() { + storage.clear(); + assertSize(0); + } + + @Test + void update() { + Resume newResume = new Resume(UUID_1, "Ivanov"); + storage.update(newResume); + Assertions.assertEquals(newResume, storage.get(UUID_1)); + } + + @Test + void getAllSorted() { + List list = storage.getAllSorted(); + Assertions.assertEquals(3, list.size()); + Assertions.assertEquals(list, Arrays.asList(RESUME_1, RESUME_2, RESUME_3)); + } + + @Test + void save() { + storage.save(RESUME_4); + assertSize(4); + assertGet(RESUME_4); + } + + @Test + void delete() { + storage.delete(UUID_1); + assertSize(2); + Assertions.assertThrows(NotExistStorageException.class, () -> storage.get(UUID_1)); + } + + @Test + void get() { + assertGet(RESUME_1); + assertGet(RESUME_2); + assertGet(RESUME_3); + } + + @Test + void getNotExist() { + Assertions.assertThrows(NotExistStorageException.class, () -> storage.get(UUID_NOT_EXIST)); + } + + @Test + void deleteNotExist() { + Assertions.assertThrows(NotExistStorageException.class, () -> storage.delete(UUID_NOT_EXIST)); + } + + @Test + public void updateNotExist() { + Assertions.assertThrows(NotExistStorageException.class, () -> storage.update(RESUME_5)); + } + +} diff --git a/test/com/learnjava/storage/ArrayStorageTest.java b/test/com/learnjava/storage/ArrayStorageTest.java new file mode 100644 index 0000000..55641fa --- /dev/null +++ b/test/com/learnjava/storage/ArrayStorageTest.java @@ -0,0 +1,7 @@ +package com.learnjava.storage; + +public class ArrayStorageTest extends AbstractArrayStorageTest { + public ArrayStorageTest() { + super(new ArrayStorage(), new ArrayStorage()); + } +} \ No newline at end of file diff --git a/test/com/learnjava/storage/FileStorageTest.java b/test/com/learnjava/storage/FileStorageTest.java new file mode 100644 index 0000000..ae62767 --- /dev/null +++ b/test/com/learnjava/storage/FileStorageTest.java @@ -0,0 +1,7 @@ +package com.learnjava.storage; +import com.learnjava.storage.serializer.ObjectStreamSerializer; +public class FileStorageTest extends AbstractStorageTest { + public FileStorageTest() { + super(new FileStorage(STORAGE_DIR, new ObjectStreamSerializer()), (new FileStorage(STORAGE_DIR_EMPTY, new ObjectStreamSerializer()))); + } +} \ No newline at end of file diff --git a/test/com/learnjava/storage/JsonPathStorageTest.java b/test/com/learnjava/storage/JsonPathStorageTest.java new file mode 100644 index 0000000..47f77c6 --- /dev/null +++ b/test/com/learnjava/storage/JsonPathStorageTest.java @@ -0,0 +1,8 @@ +package com.learnjava.storage; +import com.learnjava.storage.serializer.JsonStreamSerializer; +public class JsonPathStorageTest extends AbstractStorageTest { + + public JsonPathStorageTest() { + super(new PathStorage(STORAGE_DIR.getAbsolutePath(), new JsonStreamSerializer()),new PathStorage(STORAGE_DIR_EMPTY.getAbsolutePath(), new JsonStreamSerializer())); + } +} \ No newline at end of file diff --git a/test/com/learnjava/storage/ListStorageTest.java b/test/com/learnjava/storage/ListStorageTest.java new file mode 100644 index 0000000..6a07954 --- /dev/null +++ b/test/com/learnjava/storage/ListStorageTest.java @@ -0,0 +1,7 @@ +package com.learnjava.storage; + +public class ListStorageTest extends AbstractStorageTest { + public ListStorageTest() { + super(new ListStorage(), new ListStorage() ); + } +} \ No newline at end of file diff --git a/test/com/learnjava/storage/MapStorageTest.java b/test/com/learnjava/storage/MapStorageTest.java new file mode 100644 index 0000000..0392669 --- /dev/null +++ b/test/com/learnjava/storage/MapStorageTest.java @@ -0,0 +1,7 @@ +package com.learnjava.storage; + +public class MapStorageTest extends AbstractStorageTest { + public MapStorageTest(){ + super(new MapStorage(), new MapStorage()); + } +} diff --git a/test/com/learnjava/storage/MapUUIDStorageTest.java b/test/com/learnjava/storage/MapUUIDStorageTest.java new file mode 100644 index 0000000..07443e0 --- /dev/null +++ b/test/com/learnjava/storage/MapUUIDStorageTest.java @@ -0,0 +1,7 @@ +package com.learnjava.storage; + +public class MapUUIDStorageTest extends AbstractStorageTest { + public MapUUIDStorageTest(){ + super(new MapUUIDStorage(), new MapUUIDStorage()); + } +} diff --git a/test/com/learnjava/storage/PathStorageTest.java b/test/com/learnjava/storage/PathStorageTest.java new file mode 100644 index 0000000..c324a7f --- /dev/null +++ b/test/com/learnjava/storage/PathStorageTest.java @@ -0,0 +1,9 @@ +package com.learnjava.storage; + +import com.learnjava.storage.serializer.DataStreamSerializer; + +public class PathStorageTest extends AbstractStorageTest { + public PathStorageTest() { + super(new PathStorage(STORAGE_DIR.getAbsolutePath(), new DataStreamSerializer()), (new PathStorage(STORAGE_DIR_EMPTY.getAbsolutePath(), new DataStreamSerializer()))); + } +} diff --git a/test/com/learnjava/storage/SortedArrayStorageTest.java b/test/com/learnjava/storage/SortedArrayStorageTest.java new file mode 100644 index 0000000..1885171 --- /dev/null +++ b/test/com/learnjava/storage/SortedArrayStorageTest.java @@ -0,0 +1,7 @@ +package com.learnjava.storage; + +public class SortedArrayStorageTest extends AbstractArrayStorageTest { + public SortedArrayStorageTest() { + super(new SortedArrayStorage(), new SortedArrayStorage()); + } +} \ No newline at end of file diff --git a/test/com/learnjava/storage/SqlStorageTest.java b/test/com/learnjava/storage/SqlStorageTest.java new file mode 100644 index 0000000..8b1a2b5 --- /dev/null +++ b/test/com/learnjava/storage/SqlStorageTest.java @@ -0,0 +1,10 @@ +package com.learnjava.storage; + +import com.learnjava.Config; + +public class SqlStorageTest extends AbstractStorageTest { + + public SqlStorageTest() { + super(Config.get().getStorage()); + } +} \ No newline at end of file diff --git a/test/com/learnjava/storage/XmlPathStorageTest.java b/test/com/learnjava/storage/XmlPathStorageTest.java new file mode 100644 index 0000000..f80d849 --- /dev/null +++ b/test/com/learnjava/storage/XmlPathStorageTest.java @@ -0,0 +1,9 @@ +package com.learnjava.storage; + +import com.learnjava.storage.serializer.XmlStreamSerializer; + +public class XmlPathStorageTest extends AbstractStorageTest { + public XmlPathStorageTest() { + super(new PathStorage(STORAGE_DIR.getAbsolutePath(), new XmlStreamSerializer()), new PathStorage(STORAGE_DIR_EMPTY.getAbsolutePath(), new XmlStreamSerializer())); + } +} diff --git a/test/com/learnjava/storage/everyStorageTest.java b/test/com/learnjava/storage/everyStorageTest.java new file mode 100644 index 0000000..8652256 --- /dev/null +++ b/test/com/learnjava/storage/everyStorageTest.java @@ -0,0 +1,21 @@ +package com.learnjava.storage; + +import org.junit.platform.suite.api.SelectClasses; +import org.junit.platform.suite.api.Suite; + +@Suite +@SelectClasses({ + ArrayStorageTest.class, + SortedArrayStorageTest.class, + ListStorageTest.class, + MapStorageTest.class, + MapUUIDStorageTest.class, + FileStorageTest.class, + PathStorageTest.class, + JsonPathStorageTest.class, + XmlPathStorageTest.class, + SqlStorageTest.class +}) +public class everyStorageTest { + +}