diff --git a/pom.xml b/pom.xml
index d0d9295..ce6f8a2 100644
--- a/pom.xml
+++ b/pom.xml
@@ -21,6 +21,11 @@
5.9.2
test
+
+ info.picocli
+ picocli
+ 4.7.6
+
@@ -35,6 +40,26 @@
maven-surefire-report-plugin
3.4.0
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+
+
+ 3.13.0
+
+
+
+ info.picocli
+ picocli-codegen
+ 4.7.6
+
+
+
+ -Aproject=${project.groupId}/${project.artifactId}
+
+
+
+
diff --git a/readme.md b/readme.md
index a17a9ac..5ac65d3 100644
--- a/readme.md
+++ b/readme.md
@@ -4,15 +4,16 @@
### Commands:
```
--e Encrypt
--d Decrypt
--bf Brute force
+ -bf, --brute-force Brute force
+ -d, DECRYPT Decrypt
+ -e, ENCRYPT Encrypt
+ -f, --file= File path
+ -k, --key= Key
```
-### Arguments:
+### Usage:
```
--k Key
--f File path
+Usage: [-de] [-bf] [-f=] [-k=]
```
### Example:
@@ -25,4 +26,4 @@
### Argument could be in any order
```
-e -f "/path/to/file.txt" -k 1
-```
\ No newline at end of file
+```
diff --git a/src/main/java/ua/com/javarush/gnew/Main.java b/src/main/java/ua/com/javarush/gnew/Main.java
index 5906828..c24ad8d 100644
--- a/src/main/java/ua/com/javarush/gnew/Main.java
+++ b/src/main/java/ua/com/javarush/gnew/Main.java
@@ -1,32 +1,11 @@
package ua.com.javarush.gnew;
-import ua.com.javarush.gnew.crypto.Cypher;
-import ua.com.javarush.gnew.file.FileManager;
-import ua.com.javarush.gnew.runner.ArgumentsParser;
-import ua.com.javarush.gnew.runner.Command;
-import ua.com.javarush.gnew.runner.RunOptions;
-
-import java.nio.file.Path;
+import picocli.CommandLine;
+import ua.com.javarush.gnew.runner.SimpleCLI;
public class Main {
public static void main(String[] args) {
- Cypher cypher = new Cypher();
- FileManager fileManager = new FileManager();
- ArgumentsParser argumentsParser = new ArgumentsParser();
- RunOptions runOptions = argumentsParser.parse(args);
-
- try {
- if (runOptions.getCommand() == Command.ENCRYPT) {
- String content = fileManager.read(runOptions.getFilePath());
- String encryptedContent = cypher.encrypt(content, runOptions.getKey());
- String fileName = runOptions.getFilePath().getFileName().toString();
- String newFileName = fileName.substring(0, fileName.length() - 4) + " [ENCRYPTED].txt";
+ int exitCode = new CommandLine(new SimpleCLI()).execute(args);
- Path newFilePath = runOptions.getFilePath().resolveSibling(newFileName);
- fileManager.write(newFilePath, encryptedContent);
- }
- } catch (Exception e) {
- System.out.println(e.getMessage());
- }
}
-}
\ No newline at end of file
+}
diff --git a/src/main/java/ua/com/javarush/gnew/crypto/Cypher.java b/src/main/java/ua/com/javarush/gnew/crypto/Cypher.java
index 2b01247..d5b1c00 100644
--- a/src/main/java/ua/com/javarush/gnew/crypto/Cypher.java
+++ b/src/main/java/ua/com/javarush/gnew/crypto/Cypher.java
@@ -1,33 +1,88 @@
package ua.com.javarush.gnew.crypto;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collections;
+import ua.com.javarush.gnew.language.LanguageFactory;
+
+import java.util.HashMap;
+import java.util.Map;
+
public class Cypher {
- private final ArrayList originalAlphabet = new ArrayList<>(Arrays.asList('A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z'));
- public String encrypt(String input, int key) {
- key = Math.negateExact(key);
+ public String decrypt(String text, int shift) {
+ var alphabetLanguage = LanguageFactory.alphabet(text);
+ shift = alphabetLanguage.absKey(shift);
+
+ return encrypt(text, alphabetLanguage.length() - shift); // Для розшифрування використовується зворотний зсув
+ }
- ArrayList rotatedAlphabet = new ArrayList<>(originalAlphabet);
- Collections.rotate(rotatedAlphabet, key);
- char[] charArray = input.toCharArray();
+ public String encrypt(String text, int shift) {
+ var alphabetLanguage = LanguageFactory.alphabet(text);
+ shift = alphabetLanguage.absKey(shift);
+ String alphabet = alphabetLanguage.getAlphabet();
- StringBuilder builder = new StringBuilder();
- for (char symbol : charArray) {
- builder.append(processSymbol(symbol, rotatedAlphabet));
+ StringBuilder encryptedText = new StringBuilder();
+ int alphabetSize = alphabet.length();
+
+ for (int i = 0; i < text.length(); i++) {
+ char c = text.charAt(i);
+ if (Character.isUpperCase(c)) {
+ int index = alphabet.indexOf(c);
+ if (index != -1) {
+ char encryptedChar = alphabet.charAt((index + shift) % alphabetSize);
+ encryptedText.append(encryptedChar);
+ } else {
+ encryptedText.append(c);
+ }
+ } else if (Character.isLowerCase(c)) {
+ int index = alphabet.toLowerCase().indexOf(c);
+ if (index != -1) {
+ char encryptedChar = alphabet.toLowerCase().charAt((index + shift) % alphabetSize);
+ encryptedText.append(encryptedChar);
+ } else {
+ encryptedText.append(c);
+ }
+ } else {
+ encryptedText.append(c); // Якщо це не буква, просто додаємо символ без змін
+ }
}
- return builder.toString();
+ return encryptedText.toString();
}
- private Character processSymbol(char symbol, ArrayList rotatedAlphabet) {
- if (!originalAlphabet.contains(symbol)) {
- return symbol;
+ public int analyzeFrequency(String text) {
+ var alphabetLanguage = LanguageFactory.alphabet(text);
+ String alphabet = alphabetLanguage.getAlphabet();
+ var letterFrequency = alphabetLanguage.getLetterFrequency();
+
+ Map frequencyMap = new HashMap<>();
+ int alphabetSize = alphabet.length();
+
+ for (char c : text.toUpperCase().toCharArray()) {
+ if (alphabet.indexOf(c) != -1) {
+ frequencyMap.put(c, frequencyMap.getOrDefault(c, 0) + 1);
+ }
}
- int index = originalAlphabet.indexOf(symbol);
- return rotatedAlphabet.get(index);
+ double minChiSquared = Double.MAX_VALUE;
+ int bestShift = 0;
+
+ for (int shift = 0; shift < alphabetSize; shift++) {
+ double chiSquared = 0.0;
+
+ for (int i = 0; i < alphabetSize; i++) {
+ char decryptedChar = alphabet.charAt((i + shift) % alphabetSize);
+ int observedCount = frequencyMap.getOrDefault(decryptedChar, 0);
+ double expectedCount = text.length() * letterFrequency[i] / 100.0;
+ chiSquared += Math.pow(observedCount - expectedCount, 2) / expectedCount;
+ }
+
+ if (chiSquared < minChiSquared) {
+ minChiSquared = chiSquared;
+ bestShift = shift;
+ }
+ }
+
+ return bestShift;
}
+
}
diff --git a/src/main/java/ua/com/javarush/gnew/language/Alphabet.java b/src/main/java/ua/com/javarush/gnew/language/Alphabet.java
new file mode 100644
index 0000000..7f6dfc3
--- /dev/null
+++ b/src/main/java/ua/com/javarush/gnew/language/Alphabet.java
@@ -0,0 +1,11 @@
+package ua.com.javarush.gnew.language;
+
+public interface Alphabet {
+
+ public String getLanguage();
+ public String getAlphabet();
+ public double[] getLetterFrequency();
+ public int absKey(int key);
+ public int length();
+
+}
diff --git a/src/main/java/ua/com/javarush/gnew/language/EnglishAlphabet.java b/src/main/java/ua/com/javarush/gnew/language/EnglishAlphabet.java
new file mode 100644
index 0000000..ae9b15e
--- /dev/null
+++ b/src/main/java/ua/com/javarush/gnew/language/EnglishAlphabet.java
@@ -0,0 +1,38 @@
+package ua.com.javarush.gnew.language;
+
+public class EnglishAlphabet implements Alphabet{
+ private final String Language = "EN";
+ private static final String ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
+ private static final double[] LETTER_FREQUENCY = {
+ 8.167, 1.492, 2.782, 4.253, 12.702, 2.228, 2.015, 6.094,
+ 6.966, 0.153, 0.772, 4.025, 2.406, 6.749, 7.507, 1.929,
+ 0.095, 5.987, 6.327, 9.056, 2.758, 0.978, 2.360, 0.150,
+ 1.974, 0.074
+ };
+
+ @Override
+ public String getLanguage() {
+ return Language;
+ }
+
+ @Override
+ public String getAlphabet() {
+ return ALPHABET;
+ }
+
+ @Override
+ public double[] getLetterFrequency() {
+ return LETTER_FREQUENCY;
+ }
+
+ @Override
+ public int absKey(int key) {
+ if(key<0) { key = ALPHABET.length()-Math.abs(key % ALPHABET.length());}
+ return key;
+ }
+
+ @Override
+ public int length() {
+ return ALPHABET.length();
+ }
+}
diff --git a/src/main/java/ua/com/javarush/gnew/language/Language.java b/src/main/java/ua/com/javarush/gnew/language/Language.java
deleted file mode 100644
index 067dd2f..0000000
--- a/src/main/java/ua/com/javarush/gnew/language/Language.java
+++ /dev/null
@@ -1,12 +0,0 @@
-package ua.com.javarush.gnew.language;
-
-import java.util.ArrayList;
-
-public abstract class Language {
-
- private final ArrayList alphabet;
-
- public Language(ArrayList alphabet) {
- this.alphabet = alphabet;
- }
-}
diff --git a/src/main/java/ua/com/javarush/gnew/language/LanguageFactory.java b/src/main/java/ua/com/javarush/gnew/language/LanguageFactory.java
new file mode 100644
index 0000000..b839b3b
--- /dev/null
+++ b/src/main/java/ua/com/javarush/gnew/language/LanguageFactory.java
@@ -0,0 +1,18 @@
+package ua.com.javarush.gnew.language;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public class LanguageFactory {
+
+ public static Alphabet alphabet(String text) {
+ Pattern pattern = Pattern.compile("[а-яА-ЯґҐєЄіІїЇ]+");
+ Matcher matcher = pattern.matcher(text);
+ if (matcher.find()) { // Український текст
+ return new UkrainianAlphabet();
+ } else { // Англійський текст
+ return new EnglishAlphabet();
+ }
+ }
+
+}
diff --git a/src/main/java/ua/com/javarush/gnew/language/UkrainianAlphabet.java b/src/main/java/ua/com/javarush/gnew/language/UkrainianAlphabet.java
new file mode 100644
index 0000000..d2594ff
--- /dev/null
+++ b/src/main/java/ua/com/javarush/gnew/language/UkrainianAlphabet.java
@@ -0,0 +1,35 @@
+package ua.com.javarush.gnew.language;
+
+public class UkrainianAlphabet implements Alphabet{
+ private final String Language = "UKR";
+ private final String ALPHABET = "АБВГҐДЕЄЖЗИІЇЙКЛМНОПРСТУФХЦЧШЩЬЮЯ";
+ private final double[] LETTER_FREQUENCY = {
+ 7.45, 4.92, 4.33, 1.06, 0.01, 2.98, 1.83, 0.77, 4.92, 5.68, 2.28, 5.61, 2.72, 4.03, 7.59,
+ 9.13, 4.54, 0.81, 7.19, 5.45, 1.58, 2.06, 2.08, 0.99, 0.43, 1.47, 1.72, 0.18, 1.24, 0.32,
+ 2.26, 1.27, 0.21
+ };
+
+ @Override
+ public String getLanguage() {
+ return Language;
+ }
+ @Override
+ public String getAlphabet() {
+ return ALPHABET;
+ }
+
+ @Override
+ public double[] getLetterFrequency() {
+ return LETTER_FREQUENCY;
+ }
+
+ @Override
+ public int absKey(int key) {
+ if(key<0) { key = ALPHABET.length()-Math.abs(key % ALPHABET.length());}
+ return key;
+ }
+ @Override
+ public int length() {
+ return ALPHABET.length();
+ }
+}
diff --git a/src/main/java/ua/com/javarush/gnew/runner/ArgumentsParser.java b/src/main/java/ua/com/javarush/gnew/runner/ArgumentsParser.java
deleted file mode 100644
index eb42462..0000000
--- a/src/main/java/ua/com/javarush/gnew/runner/ArgumentsParser.java
+++ /dev/null
@@ -1,62 +0,0 @@
-package ua.com.javarush.gnew.runner;
-
-import java.nio.file.Path;
-
-public class ArgumentsParser {
- public RunOptions parse(String[] args) {
- Command command = null;
- Integer key = null;
- Path filePath = null;
-
- for (int i = 0; i < args.length; i++) {
- String arg = args[i];
-
- switch (arg) {
- case "-e":
- command = Command.ENCRYPT;
- break;
-
- case "-d":
- command = Command.DECRYPT;
- break;
-
- case "-bf":
- command = Command.BRUTEFORCE;
- break;
- case "-k":
- if (i + 1 < args.length) {
- key = Integer.parseInt(args[++i]);
- } else {
- throw new IllegalArgumentException("Missing value for key");
- }
- break;
-
- case "-f":
- if (i + 1 < args.length) {
- filePath = Path.of(args[++i]);
- } else {
- throw new IllegalArgumentException("Missing value for file");
- }
- break;
-
- default:
- throw new IllegalArgumentException("Unknown argument: " + arg);
- }
- }
-
- if (command == null) {
- throw new IllegalArgumentException("Command (-e, -d, or -bf) is required");
- }
-
- if (key == null) {
- throw new IllegalArgumentException("Key is required for encrypt or decrypt mode");
- }
-
- if (filePath == null) {
- throw new IllegalArgumentException("File path is required");
- }
-
- return new RunOptions(command, key, filePath);
- }
-
-}
diff --git a/src/main/java/ua/com/javarush/gnew/runner/Command.java b/src/main/java/ua/com/javarush/gnew/runner/Command.java
deleted file mode 100644
index 54ddeac..0000000
--- a/src/main/java/ua/com/javarush/gnew/runner/Command.java
+++ /dev/null
@@ -1,7 +0,0 @@
-package ua.com.javarush.gnew.runner;
-
-public enum Command {
- ENCRYPT,
- DECRYPT,
- BRUTEFORCE
-}
\ No newline at end of file
diff --git a/src/main/java/ua/com/javarush/gnew/runner/RunOptions.java b/src/main/java/ua/com/javarush/gnew/runner/RunOptions.java
deleted file mode 100644
index 45e700b..0000000
--- a/src/main/java/ua/com/javarush/gnew/runner/RunOptions.java
+++ /dev/null
@@ -1,54 +0,0 @@
-package ua.com.javarush.gnew.runner;
-
-import java.nio.file.Path;
-
-public class RunOptions {
- /**
- * Command to be executed.
- * -e option for encryption.
- * -d option for decryption.
- * -b option for brute force.
- */
- private final Command command;
-
- /**
- * Key to be used for encryption or decryption.
- * For encryption mode, this is the shift value.
- * -k option is required.
- */
- private final Integer key;
-
- /**
- * Path to the file to be processed.
- * For encryption and decryption modes, this is the file to be encrypted or decrypted.
- * -f option is required.
- */
- private final Path filePath;
-
- public RunOptions(Command command, Integer key, Path filePath) {
- this.command = command;
- this.key = key;
- this.filePath = filePath;
- }
-
- public Command getCommand() {
- return command;
- }
-
- public Integer getKey() {
- return key;
- }
-
- public Path getFilePath() {
- return filePath;
- }
-
- @Override
- public String toString() {
- return "CommandOptions{" +
- "command='" + command + '\'' +
- ", key=" + key +
- ", filePath=" + filePath +
- '}';
- }
-}
diff --git a/src/main/java/ua/com/javarush/gnew/runner/SimpleCLI.java b/src/main/java/ua/com/javarush/gnew/runner/SimpleCLI.java
new file mode 100644
index 0000000..e25b294
--- /dev/null
+++ b/src/main/java/ua/com/javarush/gnew/runner/SimpleCLI.java
@@ -0,0 +1,108 @@
+package ua.com.javarush.gnew.runner;
+
+import picocli.CommandLine;
+import ua.com.javarush.gnew.crypto.Cypher;
+import ua.com.javarush.gnew.file.FileManager;
+
+import java.io.Console;
+import java.io.IOException;
+import java.nio.file.Path;
+import java.util.Scanner;
+
+public class SimpleCLI implements Runnable {
+ private final FileManager fileManager = new FileManager();
+ private final Cypher cypher = new Cypher();
+ Scanner console = new Scanner(System.in);
+ /**
+ * -e Encrypt
+ * -d Decrypt
+ * -bf Brute force
+ * -k Key
+ * -f File path
+ */
+
+ @CommandLine.Option(names = {"-e", "ENCRYPT"}, description = "Encrypt", defaultValue = "false")
+ private boolean encrypt;
+ @CommandLine.Option(names = {"-d", "DECRYPT"}, description = "Decrypt", defaultValue = "false")
+ private boolean decrypt;
+ @CommandLine.Option(names = {"-bf", "--brute-force"}, description = "Brute force", defaultValue = "false")
+ private boolean bruteForce;
+ @CommandLine.Option(names = {"-f", "--file"}, description = "File path", arity = "0..1", interactive = true)
+ Path file;
+ // @CommandLine.Option(names = { "-k", "--key" }, description = "Key", defaultValue = "0")
+ @CommandLine.Option(names = {"-k", "--key"}, arity = "0..1", interactive = true)
+ Integer key;
+
+//,defaultValue = "0"
+
+ @Override
+ public void run() {
+// System.out.println(" Encrypt: "+encrypt+";\n Decrypt: "+decrypt+";\n Brute force: "+bruteForce
+// + ";\n File path: "+file.getParent()+";\n Key: "+key);
+
+// key = Math.abs(key);
+ if ( file == null) {
+ System.out.print("Enter value for --file: ");
+ file = Path.of(console.nextLine());
+ }
+ if (!bruteForce && key == null) {
+ System.out.print("Enter value for --key: ");
+ key = console.nextInt();
+ }
+
+ if (encrypt) {
+ try {
+ String mess = fileManager.read(file);
+ String fileName = file.getFileName().toString();
+ String newFileName = fileName.substring(0, fileName.length() - 4) + " [ENCRYPTED].txt";
+ Path out = (file.resolveSibling(newFileName));
+// System.out.println(out);
+ String encryptedData = cypher.encrypt(mess, key);
+// System.out.println(encryptedData);
+ fileManager.write(out, encryptedData);
+
+ } catch (IOException e) {
+ System.out.println("Error: " + e.getMessage());
+ throw new RuntimeException(e);
+ }
+ }
+ if (decrypt) {
+ try {
+ String mess = fileManager.read(file);
+ String fileName = file.getFileName().toString().replaceAll(" \\[ENCRYPTED\\]", "");
+ String newFileName = fileName.substring(0, fileName.length() - 4) + " [DECRYPTED].txt";
+ Path out = (file.resolveSibling(newFileName));
+// System.out.println(out);
+ String encryptedData = cypher.decrypt(mess, key);
+// System.out.println(encryptedData);
+ fileManager.write(out, encryptedData);
+
+ } catch (IOException e) {
+ System.out.println("Error: " + e.getMessage());
+ throw new RuntimeException(e);
+ }
+ }
+
+ if (bruteForce) {
+ try {
+ String mess = fileManager.read(file);
+ String fileName = file.getFileName().toString().replaceAll(" \\[ENCRYPTED\\]", "");
+ String newFileName = fileName.substring(0, fileName.length() - 4) + " [DECRYPTED].txt";
+ Path out = (file.resolveSibling(newFileName));
+// System.out.println(out);
+ key = cypher.analyzeFrequency(mess);
+// System.out.println("Key: "+key);
+ String encryptedData = cypher.decrypt(mess, key);
+// System.out.println(encryptedData);
+ fileManager.write(out, encryptedData);
+
+ } catch (IOException e) {
+ System.out.println("Error: " + e.getMessage());
+ throw new RuntimeException(e);
+ }
+ }
+
+
+ }
+}
+
diff --git a/src/test/java/ua/com/javarush/gnew/MainTest.java b/src/test/java/ua/com/javarush/gnew/MainTest.java
index b7f6356..89d57f6 100644
--- a/src/test/java/ua/com/javarush/gnew/MainTest.java
+++ b/src/test/java/ua/com/javarush/gnew/MainTest.java
@@ -15,7 +15,7 @@
import static org.junit.jupiter.api.Assertions.*;
class MainTest {
- private static final boolean UKRAINIAN_LANGUAGE_TEST = false;
+ private static final boolean UKRAINIAN_LANGUAGE_TEST = true;
private static final String ENCRYPT_COMMAND = "-e";
private static final String DECRYPT_COMMAND = "-d";
private static final String BF_COMMAND = "-bf";