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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions src/main/java/com/javarush/kostromin/ConsoleRunner.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.javarush.kostromin;

import com.javarush.kostromin.view.console.ConsoleApplication;

public class ConsoleRunner {
public static void main(String[] args) {
new ConsoleApplication().run();
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Обычно при сборке приложения через конструктор передают все необходимые зависимости

}
}
23 changes: 23 additions & 0 deletions src/main/java/com/javarush/kostromin/command/BaseCipher.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package com.javarush.kostromin.command;

import static com.javarush.kostromin.constant.Alphabet.ALPHABET;

public abstract class BaseCipher {

protected String processText(String text, int shift) {
StringBuilder result = new StringBuilder();
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

При таком способе кодирования объем шифруемого файла ограничен размерами памяти, если бы это было на уровне потоков ввода и вывода то такого ограничения не было бы

int n = ALPHABET.length();
shift = ((shift % n) + n) % n;

for (char c : text.toCharArray()) {
int index = ALPHABET.indexOf(c);
if (index != -1) {
int newIndex = (index + shift) % n;
result.append(ALPHABET.charAt(newIndex));
} else {
result.append(c);
}
}
return result.toString();
}
}
40 changes: 40 additions & 0 deletions src/main/java/com/javarush/kostromin/command/BruteForceMethod.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package com.javarush.kostromin.command;
import com.javarush.kostromin.constant.Alphabet;

import java.util.Arrays;
import java.util.List;

public class BruteForceMethod {
private static final List<String> COMMON_WORDS = Arrays.asList(
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Довольно неплохой способ но жёстко привязан к русскому языку. Анализ на уровне статистики опорного текста был бы в целом лучше, но наверное он для четвёртого способа будет наиболее полезен

" и ", " в ", " не ", " на ", " что ", " с ", " это ", " как ", " а ", "потому", "так"
);

public int findKey(String encryptedText) {
DecryptMethodCaesar decryptor = new DecryptMethodCaesar();
int maxMatches = -1;
int bestKey = 0;
int alphabetLength = Alphabet.ALPHABET.length();

for (int key = 1; key < alphabetLength; key++) {
String decrypted = decryptor.decrypt(encryptedText, key);
int matches = countCommonWords(decrypted);

if (matches > maxMatches) {
maxMatches = matches;
bestKey = key;
}
}
return bestKey;
}

private int countCommonWords(String text) {
String lowerText = text.toLowerCase();
int count = 0;
for (String word : COMMON_WORDS) {
if (lowerText.contains(word)) {
count++;
}
}
return count;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.javarush.kostromin.command;

public class DecryptMethodCaesar extends BaseCipher {
public String decrypt(String text, int key) {
return processText(text, -key);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.javarush.kostromin.command;

public class EncryptMethodCaesar extends BaseCipher {
public String encrypt(String text, int key) {
return processText(text, key);
}
}
7 changes: 7 additions & 0 deletions src/main/java/com/javarush/kostromin/constant/Alphabet.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.javarush.kostromin.constant;

public class Alphabet {
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Можно сделать интерфейсом, а если оставлять класс то тут полезен будет приватный конструктор

public static final String ALPHABET =
"абвгдеёжзийклмнопрстуфхцчшщъыьэюяАБВГДЕЁЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯ0123456789 .,!?:;-()";

}
10 changes: 10 additions & 0 deletions src/main/java/com/javarush/kostromin/constant/Constants.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.javarush.kostromin.constant;

import java.io.File;

public class Constants {
public static final String TXT_FOLDER = System.getProperty("user.dir") +
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

То же самое

File.separator +
"text" +
File.separator;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
package com.javarush.kostromin.controller;

import com.javarush.kostromin.command.BruteForceMethod;
import com.javarush.kostromin.command.DecryptMethodCaesar;
import com.javarush.kostromin.command.EncryptMethodCaesar;
import com.javarush.kostromin.constant.Alphabet;

import java.io.File;
import java.nio.file.Files;
import java.nio.file.Paths;

public class MainController {
public void executeCommand(String command, String[] args) {
try {
switch (command) {
case "encrypt":
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

В таких случаях полезно выносить ключи в константы

validateFileExists(args[0]);
validateKey(args[2]);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Для аргументов тоже стоило бы выделить переменные с понятными именами

encryptFile(args[0], args[1], Integer.parseInt(args[2]));
break;
case "decrypt":
validateFileExists(args[0]);
validateKey(args[2]);
decryptFile(args[0], args[1], Integer.parseInt(args[2]));
break;
case "bruteforce":
validateFileExists(args[0]);
bruteForceFile(args[0], args[1]);
break;
default:
System.out.println("Unknown command: " + command);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ввод и вывод лучше всего выполнять на уровне взаимодействия с пользователем а не логических конструкциях приложения, это позволяет легко менять тип приложения

}
} catch (RuntimeException e) {
System.out.println("Error: " + e.getMessage());
}
}

private void validateFileExists(String filePath) {
if (!new File(filePath).exists()) {
throw new IllegalArgumentException("File does not exist: " + filePath);
}
}

private void validateKey(String keyFromKeyboard) {
try {
int key = Integer.parseInt(keyFromKeyboard);
int alphabetLength = Alphabet.ALPHABET.length();
if (key < 0 || key >= alphabetLength) {
throw new IllegalArgumentException("Key must be between 0 and " +
(alphabetLength - 1));
}
} catch (NumberFormatException e) {
throw new IllegalArgumentException("Key must be a valid integer");
}
}

private void encryptFile(String sourceFile, String destFile, int key) {
try {
String contentText = new String(Files.readAllBytes(Paths.get(sourceFile)));
EncryptMethodCaesar encryptor = new EncryptMethodCaesar();
String encryptedContent = encryptor.encrypt(contentText, key);
Files.write(Paths.get(destFile), encryptedContent.getBytes());
System.out.println("File encrypted successfully with key: " + key);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Тоже самое, тут лучше не выводить явно на консоль а вернуть в виде какого-то результата

} catch (Exception e) {
throw new RuntimeException("Encryption failed: " + e.getMessage());
}
}

private void decryptFile(String sourceFile, String destFile, int key) {
try {
String contentText = new String(Files.readAllBytes(Paths.get(sourceFile)));
DecryptMethodCaesar decryptor = new DecryptMethodCaesar();
String decryptedContent = decryptor.decrypt(contentText, key);
Files.write(Paths.get(destFile), decryptedContent.getBytes());
System.out.println("File decrypted successfully with key: " + key);
} catch (Exception e) {
throw new RuntimeException("Decryption failed: " + e.getMessage());
}
}

private void bruteForceFile(String sourceFile, String destFile) {
try {
String contentText = new String(Files.readAllBytes(Paths.get(sourceFile)));
BruteForceMethod bruteForce = new BruteForceMethod();
int foundKey = bruteForce.findKey(contentText);

DecryptMethodCaesar decryptor = new DecryptMethodCaesar();
String decryptedContent = decryptor.decrypt(contentText, foundKey);
Files.write(Paths.get(destFile), decryptedContent.getBytes());

System.out.println("Brute force completed. Found key: " + foundKey);
} catch (Exception e) {
throw new RuntimeException("Brute force failed: " + e.getMessage());
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
package com.javarush.kostromin.view.console;

import com.javarush.kostromin.constant.Alphabet;
import com.javarush.kostromin.controller.MainController;

import java.io.File;
import java.util.Scanner;

import static com.javarush.kostromin.constant.Constants.TXT_FOLDER;

public class ConsoleApplication {
private final MainController controller = new MainController();
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Контроллер и сканер лучше создать и передать через конструктор


public void run() {
Scanner scanner = new Scanner(System.in);
while (true) {
printMenu();
String choice = scanner.nextLine();
System.out.println(TXT_FOLDER);
switch (choice) {
case "1":
executeEncryptCommand(scanner);
break;
case "2":
executeDecryptCommand(scanner);
break;
case "3":
executeBruteForceCommand(scanner);
break;
case "4":
return;
default:
System.out.println("Invalid option");
}
}
}


private void executeEncryptCommand(Scanner scanner) {
System.out.println("Enter input file [Push 'Enter' for default: text/text.txt]:");
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Да именно на этом уровне ввод вывод наиболее уместен

String inputFile = getInputFilePath(scanner, "text.txt");

System.out.println("Enter output file [Push 'Enter' for for default: text/encrypted.txt]:");
String outputFile = getOutputFilePath(scanner, "encrypted.txt");

System.out.println("Enter key:");
int key = getValidKey(scanner);

controller.executeCommand("encrypt", new String[]{inputFile, outputFile, String.valueOf(key)});
}

private void executeDecryptCommand(Scanner scanner) {
System.out.println("Enter input file [Push 'Enter' for default: text/encrypted.txt]:");
String inputFile = getInputFilePath(scanner, "encrypted.txt");

System.out.println("Enter output file [Push 'Enter' for default: text/decrypted.txt]:");
String outputFile = getOutputFilePath(scanner, "decrypted.txt");

System.out.println("Enter key:");
int key = getValidKey(scanner);

controller.executeCommand("decrypt", new String[]{inputFile, outputFile, String.valueOf(key)});
}

private void executeBruteForceCommand(Scanner scanner) {
System.out.println("Enter input file [Push 'Enter' for default: text/encrypted.txt]:");
String inputFile = getInputFilePath(scanner, "encrypted.txt");

System.out.println("Enter output file [Push 'Enter' for default: text/decrypted_bruteforce.txt]:");
String outputFile = getOutputFilePath(scanner, "decrypted_bruteforce.txt");

controller.executeCommand("bruteforce", new String[]{inputFile, outputFile});
}

private String getInputFilePath(Scanner scanner, String defaultPath) {
String userInputPath = scanner.nextLine().trim();
if (userInputPath.isEmpty()) {
userInputPath = TXT_FOLDER + defaultPath;
}

if (!new File(userInputPath).exists()) {
System.out.println("File does not exist: " + userInputPath);
System.out.println("Please enter a valid file path:");
return getInputFilePath(scanner, defaultPath);
}

return userInputPath;
}

private String getOutputFilePath(Scanner scanner, String defaultPath) {
String userInputPath = scanner.nextLine().trim();
if (userInputPath.isEmpty()) {
return TXT_FOLDER + defaultPath;
} else {
String pathWithTxt = ensureTxtExtension(userInputPath);
if (!pathWithTxt.contains(File.separator) && !pathWithTxt.contains("/") && !pathWithTxt.contains("\\")) {
return TXT_FOLDER + pathWithTxt;
}

return pathWithTxt;
}
}

private String ensureTxtExtension(String filePath) {
if (filePath.endsWith(File.separator)) {
return filePath + "output.txt";
}
if (!filePath.toLowerCase().endsWith(".txt")) {
if (filePath.contains(".")) {
int lastDotIndex = filePath.lastIndexOf('.');
filePath = filePath.substring(0, lastDotIndex) + ".txt";
} else {
filePath += ".txt";
}
}

return filePath;
}

private int getValidKey(Scanner scanner) {
while (true) {
try {
String userInput = scanner.nextLine().trim();
int key = Integer.parseInt(userInput);

if (key < 0 || key >= Alphabet.ALPHABET.length()) {
System.out.println("Key must be between 0 and " +
(Alphabet.ALPHABET.length() - 1));
continue;
}

return key;
} catch (NumberFormatException e) {
System.out.println("Please enter a valid integer key:");
}
}
}

private void printMenu() {
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Класс получился достаточно большим в принципе можно разбить его на функциональные фрагменты

System.out.println("\n Caesar Cipher Application ");
System.out.println("-----------------------------");
System.out.println("|1. Encrypt");
System.out.println("|2. Decrypt with key");
System.out.println("|3. Brute force");
System.out.println("|4. Exit");
System.out.println("-----------------------------");
System.out.println("|Choose an option: ");
System.out.println("-----------------------------");
}
}