diff --git a/pom.xml b/pom.xml
index a519ba9..f750ea4 100644
--- a/pom.xml
+++ b/pom.xml
@@ -36,5 +36,15 @@
commons-math3
3.6.1
+
+ junit
+ junit
+ test
+
+
+ org.junit.jupiter
+ junit-jupiter
+ test
+
\ No newline at end of file
diff --git a/src/main/java/com/javarush/bakhtin/Application.java b/src/main/java/com/javarush/bakhtin/Application.java
new file mode 100644
index 0000000..c0e0040
--- /dev/null
+++ b/src/main/java/com/javarush/bakhtin/Application.java
@@ -0,0 +1,7 @@
+package com.javarush.bakhtin;
+
+public class Application {
+ public static void main(String[] args) {
+ new Console().run();
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/javarush/bakhtin/Caesar.java b/src/main/java/com/javarush/bakhtin/Caesar.java
new file mode 100644
index 0000000..e9fad31
--- /dev/null
+++ b/src/main/java/com/javarush/bakhtin/Caesar.java
@@ -0,0 +1,34 @@
+package com.javarush.bakhtin;
+
+import java.io.FileReader;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.nio.file.Path;
+
+public class Caesar {
+
+ private final int FIRST_UNICODE_LETTER = 9;
+ private final int LAST_UNICODE_LETTER = 1104;
+ private final int NUM_OF_UNICODE_LETTERS = LAST_UNICODE_LETTER - FIRST_UNICODE_LETTER;
+
+ public void decode(int key, Path from, Path to) throws IOException {
+ encode(-key, from, to);
+ }
+
+ public void encode(int key, Path from, Path to) throws IOException {
+ try (FileReader reader = FileUtil.toFileReader(from);
+ FileWriter writer = FileUtil.toFileWriter(to)) {
+ while (reader.ready()) {
+ int inputUnicodeCode = reader.read();
+ char resultChar = encodeChar(key, inputUnicodeCode);
+ writer.write(resultChar);
+ }
+ }
+ }
+
+ protected char encodeChar(int key, int unicodeCode) {
+ return (char) ((unicodeCode - FIRST_UNICODE_LETTER + key % NUM_OF_UNICODE_LETTERS + NUM_OF_UNICODE_LETTERS)
+ % NUM_OF_UNICODE_LETTERS + FIRST_UNICODE_LETTER); // Общая формула для циклического сдвига
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/com/javarush/bakhtin/Console.java b/src/main/java/com/javarush/bakhtin/Console.java
new file mode 100644
index 0000000..391486a
--- /dev/null
+++ b/src/main/java/com/javarush/bakhtin/Console.java
@@ -0,0 +1,22 @@
+package com.javarush.bakhtin;
+
+import java.util.InputMismatchException;
+
+public class Console {
+
+ private final MenuController menuController = new MenuController();
+
+ protected void run() {
+ while (true) {
+ menuController.printCommands();
+ try {
+ int answer = menuController.getUserCommand();
+ menuController.executeCommand(answer);
+ } catch (InputMismatchException e) {
+ System.err.println("Ошибка, введите число");
+ } catch (Exception e) {
+ System.err.println(e.getMessage());
+ }
+ }
+ }
+}
diff --git a/src/main/java/com/javarush/bakhtin/FileUtil.java b/src/main/java/com/javarush/bakhtin/FileUtil.java
new file mode 100644
index 0000000..6ad1305
--- /dev/null
+++ b/src/main/java/com/javarush/bakhtin/FileUtil.java
@@ -0,0 +1,31 @@
+package com.javarush.bakhtin;
+
+import java.io.FileNotFoundException;
+import java.io.FileReader;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+
+public class FileUtil {
+
+ public static FileWriter toFileWriter(Path toPath) throws IOException {
+ try {
+ if (!Files.exists(toPath)) {
+ Files.createFile(toPath);
+ }
+ return new FileWriter(toPath.toFile());
+ } catch (FileNotFoundException e) {
+ throw new RuntimeException("Ошибка, файл не найден");
+ }
+ }
+
+ public static FileReader toFileReader(Path fromPath){
+ try {
+ return new FileReader(fromPath.toFile());
+ } catch (FileNotFoundException e) {
+ throw new RuntimeException("Ошибка, файл не найден");
+ }
+ }
+
+}
diff --git a/src/main/java/com/javarush/bakhtin/MenuController.java b/src/main/java/com/javarush/bakhtin/MenuController.java
new file mode 100644
index 0000000..6c71781
--- /dev/null
+++ b/src/main/java/com/javarush/bakhtin/MenuController.java
@@ -0,0 +1,43 @@
+package com.javarush.bakhtin;
+
+import com.javarush.bakhtin.command.*;
+
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Scanner;
+
+public class MenuController {
+
+ private static final Scanner consoleInput = new Scanner(System.in);
+
+ private static final String MENU_MESSAGE = "Введите цифру, соответствующую команде:";
+ private static final HashMap menuItemMap = new HashMap<>(3);
+
+ protected MenuController() {
+ menuItemMap.put(1, new EncoderCommand());
+ menuItemMap.put(2, new DecoderCommand());
+ menuItemMap.put(3, new BruteForce());
+ menuItemMap.put(4, new ExitCommand());
+ }
+
+ protected void executeCommand(int answer) throws IOException {
+ menuItemMap.get(answer).execute();
+ }
+
+ protected void printCommands() {
+ System.out.println(MENU_MESSAGE);
+ for (var menuItem : MenuController.menuItemMap.entrySet()) {
+ System.out.print(menuItem.getValue().getCommandName());
+ System.out.print(" - ");
+ System.out.println(menuItem.getKey());
+ }
+ }
+
+ public int getUserCommand() {
+ int answer = Integer.parseInt(consoleInput.nextLine());
+ if (answer < 1 || answer > 4) {
+ throw new RuntimeException("Ошибка, введите число от 1 до 4");
+ }
+ return answer;
+ }
+}
diff --git a/src/main/java/com/javarush/bakhtin/command/BruteForce.java b/src/main/java/com/javarush/bakhtin/command/BruteForce.java
new file mode 100644
index 0000000..25539c9
--- /dev/null
+++ b/src/main/java/com/javarush/bakhtin/command/BruteForce.java
@@ -0,0 +1,72 @@
+package com.javarush.bakhtin.command;
+
+import com.javarush.bakhtin.Caesar;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.*;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public class BruteForce implements MenuCommand {
+
+ private static final String COMMAND_NAME = "Брутфорс";
+
+ public String getCommandName() {
+ return COMMAND_NAME;
+ }
+
+ private static final Pattern WORD_PATTERN = Pattern.compile("[а-я]{4,20}");
+
+ private Set wordSet = new HashSet<>();
+ private Set encodedWordSet = new HashSet<>();
+
+ private final CaesarParamReader caesarParamReader = new CaesarParamReader();
+ private final Caesar caesar = new Caesar();
+
+ public void makeAWordListFromDictionary() throws IOException {
+ String dict = Files.readString(caesarParamReader.getDictFromUser());
+ Matcher matcher = WORD_PATTERN.matcher(dict);
+ while (matcher.find()) {
+ wordSet.add(matcher.group());
+ }
+ }
+
+ private void makeAWordListFromDecodedFile(Path path) throws IOException {
+ encodedWordSet.clear();
+ String text = Files.readString(path);
+ Matcher matcher = WORD_PATTERN.matcher(text);
+ while (matcher.find()) {
+ encodedWordSet.add(matcher.group());
+ }
+ }
+
+ public void execute() throws IOException {
+ makeAWordListFromDictionary();
+ int maxNumOfMatches = 0;
+ int shiftOfMaxNumOfMatches = 0;
+ Path encodedPath = caesarParamReader.getEncodedFromUser();
+ Path decodedPath = caesarParamReader.getDecodedFromUser();
+
+ for (int shift = -50; shift <= 50; shift++) {
+ System.out.println("Старт " + shift);
+ caesar.decode(shift, encodedPath, decodedPath);
+ makeAWordListFromDecodedFile(decodedPath);
+ int findCounter = 0;
+ for (String string : encodedWordSet) {
+ if (wordSet.contains(string)) {
+ findCounter++;
+ }
+ }
+ System.out.println("Стоп: найдено слов:" + findCounter);
+ if (findCounter > maxNumOfMatches) {
+ maxNumOfMatches = findCounter;
+ shiftOfMaxNumOfMatches = shift;
+ }
+ }
+ System.out.println("Правильный ключ: " + shiftOfMaxNumOfMatches);
+ caesar.decode(shiftOfMaxNumOfMatches, encodedPath, decodedPath);
+ System.out.println("Брутфорс завершен");
+ }
+}
diff --git a/src/main/java/com/javarush/bakhtin/command/CaesarParamReader.java b/src/main/java/com/javarush/bakhtin/command/CaesarParamReader.java
new file mode 100644
index 0000000..5dda146
--- /dev/null
+++ b/src/main/java/com/javarush/bakhtin/command/CaesarParamReader.java
@@ -0,0 +1,68 @@
+package com.javarush.bakhtin.command;
+
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.Scanner;
+
+public class CaesarParamReader {
+
+ private static final String KEY_INPUT_MESSAGE = "Введите ключ шифрования, по умолчанию - 1:";
+ private static final String FROM_PATH_INPUT_MESSAGE = "Введите путь, откуда вы хотите считать данные или нажмите enter, чтобы выбрать ";
+ private static final String DICT_PATH_INPUT_MESSAGE = "Введите путь к словарю или нажмите enter, чтобы выбрать ";
+ private static final String ENCODED_PATH_INPUT_MESSAGE = "Введите путь к зашифрованному файлу или нажмите enter, чтобы выбрать ";
+ private static final String DECODED_PATH_INPUT_MESSAGE = "Введите путь к расшифрованному файлу или нажмите enter, чтобы выбрать ";
+ private static final Path DEFAULT_DICT_PATH = Path.of("text", "dict.txt");
+ private static final Path DEFAULT_ENCODED_PATH = Path.of("text", "encrypted.txt");
+ private static final Path DEFAULT_DECODED_PATH = Paths.get("text", "decrypted.txt");
+
+ private final Scanner consoleScanner = new Scanner(System.in);
+
+ private static int key = 1;
+ private static final CaesarParamReader instance = new CaesarParamReader();
+
+ public static CaesarParamReader getInstance() {
+ return instance;
+ }
+
+ private Path getPath(Path defaultPath) {
+ String consoleString = consoleScanner.nextLine();
+ if (!consoleString.isEmpty()) {
+ return Path.of(consoleString);
+ }
+ return defaultPath;
+ }
+
+ protected int getKeyFromUser() {
+ System.out.print(KEY_INPUT_MESSAGE);
+ try {
+ int inputKey = Integer.parseInt(consoleScanner.nextLine());
+ if (inputKey != 0) {
+ key = inputKey;
+ }
+ return key;
+ } catch (NumberFormatException e) {
+ throw new RuntimeException("Ошибка, введите число");
+ }
+ }
+
+ protected Path getFromPathFromUser(Path defaultFrom) {
+ System.out.println(FROM_PATH_INPUT_MESSAGE + defaultFrom.getFileName());
+ return getPath(defaultFrom);
+ }
+
+ protected Path getDictFromUser() {
+ System.out.println(DICT_PATH_INPUT_MESSAGE + DEFAULT_DICT_PATH.getFileName());
+ return getPath(DEFAULT_DICT_PATH);
+ }
+
+ protected Path getEncodedFromUser() {
+ System.out.println(ENCODED_PATH_INPUT_MESSAGE + DEFAULT_ENCODED_PATH.getFileName());
+ return getPath(DEFAULT_ENCODED_PATH);
+ }
+
+ protected Path getDecodedFromUser() {
+ System.out.println(DECODED_PATH_INPUT_MESSAGE + DEFAULT_DECODED_PATH.getFileName());
+ return getPath(DEFAULT_DECODED_PATH);
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/com/javarush/bakhtin/command/DecoderCommand.java b/src/main/java/com/javarush/bakhtin/command/DecoderCommand.java
new file mode 100644
index 0000000..7d29366
--- /dev/null
+++ b/src/main/java/com/javarush/bakhtin/command/DecoderCommand.java
@@ -0,0 +1,24 @@
+package com.javarush.bakhtin.command;
+
+import com.javarush.bakhtin.Caesar;
+
+import java.io.IOException;
+import java.nio.file.Path;
+
+public class DecoderCommand implements MenuCommand {
+
+ private static final String COMMAND_NAME = "Расшифровать";
+
+ public String getCommandName() {
+ return COMMAND_NAME;
+ }
+
+ public void execute() throws IOException {
+ var reader = CaesarParamReader.getInstance();
+ int key = reader.getKeyFromUser();
+ Path from = reader.getEncodedFromUser();
+ Path to = reader.getDecodedFromUser();
+ new Caesar().decode(key, from, to);
+ }
+
+}
diff --git a/src/main/java/com/javarush/bakhtin/command/EncoderCommand.java b/src/main/java/com/javarush/bakhtin/command/EncoderCommand.java
new file mode 100644
index 0000000..d0b2db1
--- /dev/null
+++ b/src/main/java/com/javarush/bakhtin/command/EncoderCommand.java
@@ -0,0 +1,27 @@
+package com.javarush.bakhtin.command;
+
+import com.javarush.bakhtin.Caesar;
+
+import java.io.IOException;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+
+public class EncoderCommand implements MenuCommand {
+
+ private static final Path DEFAULT_TEXT_PATH = Paths.get("text", "text.txt");
+ private static final String COMMAND_NAME = "Зашифровать";
+
+ public String getCommandName() {
+ return COMMAND_NAME;
+ }
+
+ public void execute() throws IOException {
+ var reader = CaesarParamReader.getInstance();
+ int key = reader.getKeyFromUser();
+ Path from = reader.getFromPathFromUser(DEFAULT_TEXT_PATH);
+ Path to = reader.getEncodedFromUser();
+
+ new Caesar().encode(key, from, to);
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/com/javarush/bakhtin/command/ExitCommand.java b/src/main/java/com/javarush/bakhtin/command/ExitCommand.java
new file mode 100644
index 0000000..47ae5bf
--- /dev/null
+++ b/src/main/java/com/javarush/bakhtin/command/ExitCommand.java
@@ -0,0 +1,15 @@
+package com.javarush.bakhtin.command;
+
+public class ExitCommand implements MenuCommand {
+
+ private static final String COMMAND_NAME = "Выход из программы";
+
+ public String getCommandName() {
+ return COMMAND_NAME;
+ }
+
+ public void execute() {
+ System.out.print("Выход из программы...");
+ System.exit(0);
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/javarush/bakhtin/command/MenuCommand.java b/src/main/java/com/javarush/bakhtin/command/MenuCommand.java
new file mode 100644
index 0000000..d997b69
--- /dev/null
+++ b/src/main/java/com/javarush/bakhtin/command/MenuCommand.java
@@ -0,0 +1,10 @@
+package com.javarush.bakhtin.command;
+
+import java.io.IOException;
+
+public interface MenuCommand {
+
+ String getCommandName();
+
+ void execute() throws IOException;
+}