From aa880ac9f81a1944cf40177a35f4518b6a9bff20 Mon Sep 17 00:00:00 2001 From: tacehex Date: Wed, 3 Sep 2025 13:06:01 +0300 Subject: [PATCH] add - package morozov --- src/main/java/com/javarush/morozov/App.java | 12 ++ .../javarush/morozov/core/CaesarCipher.java | 159 ++++++++++++++++++ .../javarush/morozov/core/FileHandler.java | 53 ++++++ .../com/javarush/morozov/ui/MainWindow.java | 98 +++++++++++ .../morozov/ui/handlers/MessageHandler.java | 32 ++++ .../listeners/BruteForceButtonListener.java | 43 +++++ .../ui/listeners/DecryptButtonListener.java | 40 +++++ .../ui/listeners/EncryptButtonListener.java | 40 +++++ .../ui/listeners/LoadFileButtonListener.java | 45 +++++ .../ui/listeners/SaveFileButtonListener.java | 50 ++++++ 10 files changed, 572 insertions(+) create mode 100644 src/main/java/com/javarush/morozov/App.java create mode 100644 src/main/java/com/javarush/morozov/core/CaesarCipher.java create mode 100644 src/main/java/com/javarush/morozov/core/FileHandler.java create mode 100644 src/main/java/com/javarush/morozov/ui/MainWindow.java create mode 100644 src/main/java/com/javarush/morozov/ui/handlers/MessageHandler.java create mode 100644 src/main/java/com/javarush/morozov/ui/listeners/BruteForceButtonListener.java create mode 100644 src/main/java/com/javarush/morozov/ui/listeners/DecryptButtonListener.java create mode 100644 src/main/java/com/javarush/morozov/ui/listeners/EncryptButtonListener.java create mode 100644 src/main/java/com/javarush/morozov/ui/listeners/LoadFileButtonListener.java create mode 100644 src/main/java/com/javarush/morozov/ui/listeners/SaveFileButtonListener.java diff --git a/src/main/java/com/javarush/morozov/App.java b/src/main/java/com/javarush/morozov/App.java new file mode 100644 index 0000000..9ed1f30 --- /dev/null +++ b/src/main/java/com/javarush/morozov/App.java @@ -0,0 +1,12 @@ +package com.javarush.morozov; + +import com.javarush.morozov.ui.MainWindow; + +public class App { + public static void main(String[] args) { + javax.swing.SwingUtilities.invokeLater(() -> { + MainWindow mainWindow = new MainWindow(); + mainWindow.setVisible(true); + }); + } +} diff --git a/src/main/java/com/javarush/morozov/core/CaesarCipher.java b/src/main/java/com/javarush/morozov/core/CaesarCipher.java new file mode 100644 index 0000000..66c0fa7 --- /dev/null +++ b/src/main/java/com/javarush/morozov/core/CaesarCipher.java @@ -0,0 +1,159 @@ +package com.javarush.morozov.core; + +import com.javarush.morozov.ui.handlers.MessageHandler; + +import java.util.*; + +public class CaesarCipher { + public enum Language { + RUSSIAN("RUS", "Русские"), + ENGLISH("ENG", "Английские"); + + private final String code; + private final String displayName; + + Language(String code, String displayName) { + this.code = code; + this.displayName = displayName; + } + + public String getCode() { + return code; + } + + public String getDisplayName() { + return displayName; + } + + @Override + public String toString() { + return displayName; + } + } + + private static final char[] RUSSIAN_ALPHABET = { + 'а', 'б', 'в', 'г', 'д', 'е', 'ж', 'з', 'и', 'й', 'к', 'л', 'м', 'н', 'о', 'п', + 'р', 'с', 'т', 'у', 'ф', 'х', 'ц', 'ч', 'ш', 'щ', 'ъ', 'ы', 'ь', 'э', 'ю', 'я', + '.', ',', '«', '»', '"', '\'', ':', '!', '?', ' ' + }; + + private static final char[] ENGLISH_ALPHABET = { + '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', + '.', ',', '«', '»', '"', '\'', ':', '!', '?', ' ' + }; + + private char[] currentAlphabet; + private Language currentLanguage; + private Set allowedChars; + + public CaesarCipher(Language language) { + setLanguage(language); + } + + public void setLanguage(Language language) { + this.currentLanguage = language; + this.currentAlphabet = language == Language.RUSSIAN ? RUSSIAN_ALPHABET : ENGLISH_ALPHABET; + this.allowedChars = new HashSet<>(); + + for (char c : currentAlphabet) { + allowedChars.add(c); + } + } + + public String encrypt(String text, int key) throws IllegalArgumentException { + validateText(text); + return processText(text, key); + } + + public String decrypt(String text, int key) throws IllegalArgumentException { + validateText(text); + return processText(text, -key); + } + + private String processText(String text, int key) { + StringBuilder result = new StringBuilder(); + text = text.toLowerCase(); + + for (char c : text.toCharArray()) { + int index = Arrays.binarySearch(currentAlphabet, c); + + if (index >= 0) { + int newIndex = (index + key) % currentAlphabet.length; + if (newIndex < 0) newIndex += currentAlphabet.length; + + result.append(currentAlphabet[newIndex]); + } else { + result.append(c); + } + } + + return result.toString(); + } + + private void validateText(String text) throws IllegalArgumentException { + for (int i = 0; i < text.length(); i++) { + char c = Character.toLowerCase(text.charAt(i)); + + if (!allowedChars.contains(c)) { + String errorMessage = "Текст содержит недопустимые символы. Разрешены только " + + currentLanguage.getDisplayName() + " символы и знаки пунктуации."; + + MessageHandler.showErrorDialog(errorMessage); + + throw new IllegalArgumentException( + String.format("Недопустимый символ '%c' в позиции %d", text.charAt(i), i) + ); + } + } + } + + /** + * Расшифровка методом BruteForce + * @param encryptedText расшифровываемый текст. + * @return Список результатов оценки метода BruteForce. + */ + public List bruteForce(String encryptedText) { + List results = new ArrayList<>(); + + for (int key = 1; key < currentAlphabet.length; key++) { + String decrypted = processText(encryptedText, -key); + int score = calculateTextScore(decrypted); + results.add(new BruteForceResult(decrypted, key, score)); + } + + results.sort((a, b) -> Integer.compare(b.score, a.score)); + + return results; + } + + public record BruteForceResult(String text, int key, int score) { + } + + private int calculateTextScore(String text) { + int score = 0; + + long spaceCount = text.chars().filter(c -> c == ' ').count(); + score += (int) (spaceCount * 10); + + String vowels = currentLanguage == Language.RUSSIAN ? "аеёиоуыэюя" : "aeiouy"; + long vowelCount = text.toLowerCase().chars() + .filter(c -> vowels.indexOf(c) >= 0) + .count(); + score += (int) (vowelCount * 3); + + String commonChars = currentLanguage == Language.RUSSIAN ? "оае" : "eta"; + long commonCount = text.toLowerCase().chars() + .filter(c -> commonChars.indexOf(c) >= 0) + .count(); + score += (int) (commonCount * 2); + + String punctuation = ".,!?;:\"'"; + + if (text.matches(".*[a-zA-Zа-яА-Я][" + punctuation + "][a-zA-Zа-яА-Я].*")) { + score -= 20; + } + + return score; + } +} \ No newline at end of file diff --git a/src/main/java/com/javarush/morozov/core/FileHandler.java b/src/main/java/com/javarush/morozov/core/FileHandler.java new file mode 100644 index 0000000..5ac5bb6 --- /dev/null +++ b/src/main/java/com/javarush/morozov/core/FileHandler.java @@ -0,0 +1,53 @@ +package com.javarush.morozov.core; + +import com.javarush.morozov.ui.handlers.MessageHandler; + +import javax.swing.filechooser.FileNameExtensionFilter; +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Paths; + +public class FileHandler { + private static final long MAX_FILE_SIZE = 10 * 1024 * 1024; // 10 MB + + public String readTextFile(File file) throws IOException { + if (!file.exists()) { + throw new IOException("Файл не существует"); + } + + if (file.length() > MAX_FILE_SIZE) { + throw new IOException("Файл слишком большой (макс. " + MAX_FILE_SIZE/1024/1024 + " МБ)"); + } + + if (!file.canRead()) { + throw new IOException("Нет прав на чтение файла"); + } + + String content = Files.readString(Paths.get(file.getPath())); + + if (content.trim().isEmpty()) { + throw new IOException("Файл пуст"); + } + + return content; + } + + public void saveTextFile(File file, String content) throws IOException { + if (file.exists() && !file.canWrite()) { + throw new IOException("Нет прав на запись в файл"); + } + + if (file.exists()) { + if (!MessageHandler.confirmFileOverwrite()) { + throw new IOException("Сохранение отменено"); + } + } + + Files.writeString(file.toPath(), content); + } + + public static FileNameExtensionFilter getTextFileFilter() { + return new FileNameExtensionFilter("Текстовые файлы (*.txt)", "txt"); + } +} diff --git a/src/main/java/com/javarush/morozov/ui/MainWindow.java b/src/main/java/com/javarush/morozov/ui/MainWindow.java new file mode 100644 index 0000000..0af9f9c --- /dev/null +++ b/src/main/java/com/javarush/morozov/ui/MainWindow.java @@ -0,0 +1,98 @@ +package com.javarush.morozov.ui; + +import com.javarush.morozov.core.CaesarCipher; +import com.javarush.morozov.core.FileHandler; +import com.javarush.morozov.ui.listeners.*; + +import javax.swing.*; +import java.awt.*; + +public class MainWindow extends JFrame { + private JTextArea inputTextArea; + private JTextArea outputTextArea; + private JButton encryptButton; + private JButton decryptButton; + private JButton bruteForceButton; + private JButton loadFileButton; + private JButton saveFileButton; + JComboBox languageComboBox; + + + public MainWindow() { + super("Шифр цезаря"); + setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); + setSize(800, 600); + setLayout(new BorderLayout()); // автоматическое управление расположением компонентов внутри контейнера + + initUI(); + setupListeners(); + } + + /** + * Инициализация UI заданными компонентами. + */ + private void initUI() { + JPanel topPanel = new JPanel(new BorderLayout()); + + JPanel headerPanel = new JPanel(new FlowLayout(FlowLayout.LEFT)); + languageComboBox = new JComboBox<>(CaesarCipher.Language.values()); + languageComboBox.setSelectedItem(CaesarCipher.Language.RUSSIAN); + headerPanel.add(new JLabel("Символы шифровки:")); + headerPanel.add(languageComboBox); + topPanel.add(headerPanel, BorderLayout.NORTH); + + JPanel buttonPanel = new JPanel(new FlowLayout(FlowLayout.CENTER, 10, 10)); + encryptButton = new JButton("Зашифровать"); + decryptButton = new JButton("Расшифровать"); + bruteForceButton = new JButton("Brute Force"); + loadFileButton = new JButton("Загрузить файл"); + saveFileButton = new JButton("Сохранить в файл"); + + buttonPanel.add(loadFileButton); + buttonPanel.add(encryptButton); + buttonPanel.add(decryptButton); + buttonPanel.add(bruteForceButton); + buttonPanel.add(saveFileButton); + topPanel.add(buttonPanel, BorderLayout.CENTER); + + inputTextArea = new JTextArea(); + outputTextArea = new JTextArea(); + outputTextArea.setEditable(false); + + JScrollPane inputScrollPane = new JScrollPane(inputTextArea); + JScrollPane outputScrollPane = new JScrollPane(outputTextArea); + + JPanel textPanel = new JPanel(new GridLayout(1, 2, 10, 10)); + textPanel.add(inputScrollPane); + textPanel.add(outputScrollPane); + + add(topPanel, BorderLayout.NORTH); + add(textPanel, BorderLayout.CENTER); + } + + /** + * Установка прослушиваний при нажатии на кнопки + */ + private void setupListeners() { + FileHandler handler = new FileHandler(); + CaesarCipher cipher = new CaesarCipher(getSelectedLanguage()); + + LoadFileButtonListener loadListener = new LoadFileButtonListener(inputTextArea, outputTextArea, handler); + SaveFileButtonListener saveListener = new SaveFileButtonListener(outputTextArea, handler); + EncryptButtonListener encryptListener = new EncryptButtonListener(inputTextArea, outputTextArea, cipher); + DecryptButtonListener decryptListener = new DecryptButtonListener(inputTextArea, outputTextArea, cipher); + BruteForceButtonListener bruteForceListener = new BruteForceButtonListener(inputTextArea, outputTextArea, cipher); + + loadFileButton.addActionListener(loadListener); + saveFileButton.addActionListener(saveListener); + encryptButton.addActionListener(encryptListener); + decryptButton.addActionListener(decryptListener); + bruteForceButton.addActionListener(bruteForceListener); + languageComboBox.addActionListener(_ -> cipher.setLanguage(getSelectedLanguage())); + + } + + private CaesarCipher.Language getSelectedLanguage() { + return (CaesarCipher.Language) languageComboBox.getSelectedItem(); + } +} diff --git a/src/main/java/com/javarush/morozov/ui/handlers/MessageHandler.java b/src/main/java/com/javarush/morozov/ui/handlers/MessageHandler.java new file mode 100644 index 0000000..8eb0f01 --- /dev/null +++ b/src/main/java/com/javarush/morozov/ui/handlers/MessageHandler.java @@ -0,0 +1,32 @@ +package com.javarush.morozov.ui.handlers; + +import javax.swing.*; + +public class MessageHandler { + public static void showErrorDialog(String message) { + JOptionPane.showMessageDialog( + null, + message, + "Ошибка", + JOptionPane.ERROR_MESSAGE + ); + } + + public static void showSuccessDialog(String message) { + JOptionPane.showMessageDialog( + null, + message, + "Успех", + JOptionPane.INFORMATION_MESSAGE + ); + } + + public static boolean confirmFileOverwrite() { + return JOptionPane.showConfirmDialog( + null, + "Файл уже существует. Перезаписать?", + "Подтверждение", + JOptionPane.YES_NO_OPTION + ) == JOptionPane.YES_OPTION; + } +} diff --git a/src/main/java/com/javarush/morozov/ui/listeners/BruteForceButtonListener.java b/src/main/java/com/javarush/morozov/ui/listeners/BruteForceButtonListener.java new file mode 100644 index 0000000..044f936 --- /dev/null +++ b/src/main/java/com/javarush/morozov/ui/listeners/BruteForceButtonListener.java @@ -0,0 +1,43 @@ +package com.javarush.morozov.ui.listeners; + +import com.javarush.morozov.core.CaesarCipher; +import com.javarush.morozov.ui.handlers.MessageHandler; + +import javax.swing.*; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.util.List; + +public class BruteForceButtonListener implements ActionListener { + private final JTextArea inputTextArea; + private final JTextArea outputTextArea; + private final CaesarCipher cipher; + + public BruteForceButtonListener(JTextArea inputTextArea, JTextArea outputTextArea, CaesarCipher cipher) { + this.inputTextArea = inputTextArea; + this.outputTextArea = outputTextArea; + this.cipher = cipher; + } + + @Override + public void actionPerformed(ActionEvent e) { + String text = inputTextArea.getText(); + + if (text == null || text.trim().isEmpty()) { + MessageHandler.showErrorDialog("Текст для расшифровки методом BruteForce оказался пустым."); + return; + } + + List results = cipher.bruteForce(text); + + StringBuilder sb = new StringBuilder("Возможные варианты: \n\n"); + + for (int i = 0; i < Math.min(3, results.size()); i++) { + CaesarCipher.BruteForceResult result = results.get(i); + sb.append(String.format("Ключ %d (уверенность %d): \n%s\n\n", + result.key(), result.score(), result.text())); + } + + outputTextArea.setText(sb.toString()); + } +} diff --git a/src/main/java/com/javarush/morozov/ui/listeners/DecryptButtonListener.java b/src/main/java/com/javarush/morozov/ui/listeners/DecryptButtonListener.java new file mode 100644 index 0000000..1076392 --- /dev/null +++ b/src/main/java/com/javarush/morozov/ui/listeners/DecryptButtonListener.java @@ -0,0 +1,40 @@ +package com.javarush.morozov.ui.listeners; + +import com.javarush.morozov.core.CaesarCipher; +import com.javarush.morozov.ui.handlers.MessageHandler; + +import javax.swing.*; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; + +public class DecryptButtonListener implements ActionListener { + private final JTextArea inputTextArea; + private final JTextArea outputTextArea; + private final CaesarCipher cipher; + + public DecryptButtonListener(JTextArea inputTextArea, JTextArea outputTextArea, CaesarCipher cipher) { + this.inputTextArea = inputTextArea; + this.outputTextArea = outputTextArea; + this.cipher = cipher; + } + + @Override + public void actionPerformed(ActionEvent e) { + String text = inputTextArea.getText(); + + if (text == null || text.trim().isEmpty()) { + MessageHandler.showErrorDialog("Текст для расшифровки оказался пустым."); + return; + } + + String keyString = JOptionPane.showInputDialog("Введите числовое значения ключа:"); + + try { + int keyInt = Integer.parseInt(keyString); + String decryptText = cipher.decrypt(text, keyInt); + outputTextArea.setText(decryptText); + } catch (NumberFormatException ex) { + MessageHandler.showErrorDialog("Ошибка: ключ должен быть числом!"); + } + } +} diff --git a/src/main/java/com/javarush/morozov/ui/listeners/EncryptButtonListener.java b/src/main/java/com/javarush/morozov/ui/listeners/EncryptButtonListener.java new file mode 100644 index 0000000..19ac17f --- /dev/null +++ b/src/main/java/com/javarush/morozov/ui/listeners/EncryptButtonListener.java @@ -0,0 +1,40 @@ +package com.javarush.morozov.ui.listeners; + +import com.javarush.morozov.core.CaesarCipher; +import com.javarush.morozov.ui.handlers.MessageHandler; + +import javax.swing.*; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; + +public class EncryptButtonListener implements ActionListener { + private final JTextArea inputTextArea; + private final JTextArea outputTextArea; + private final CaesarCipher cipher; + + public EncryptButtonListener(JTextArea inputTextArea, JTextArea outputTextArea, CaesarCipher cipher) { + this.inputTextArea = inputTextArea; + this.outputTextArea = outputTextArea; + this.cipher = cipher; + } + + @Override + public void actionPerformed(ActionEvent e) { + String text = inputTextArea.getText(); + + if (text == null || text.trim().isEmpty()) { + MessageHandler.showErrorDialog("Текст для зашифровки оказался пустым."); + return; + } + + String keyString = JOptionPane.showInputDialog("Введите числовое значения ключа:"); + + try { + int keyInt = Integer.parseInt(keyString); + String encryptText = cipher.encrypt(text, keyInt); + outputTextArea.setText(encryptText); + } catch (NumberFormatException ex) { + MessageHandler.showErrorDialog("Ошибка: ключ должен быть числом!"); + } + } +} diff --git a/src/main/java/com/javarush/morozov/ui/listeners/LoadFileButtonListener.java b/src/main/java/com/javarush/morozov/ui/listeners/LoadFileButtonListener.java new file mode 100644 index 0000000..7c400f4 --- /dev/null +++ b/src/main/java/com/javarush/morozov/ui/listeners/LoadFileButtonListener.java @@ -0,0 +1,45 @@ +package com.javarush.morozov.ui.listeners; + + +import com.javarush.morozov.core.FileHandler; + +import javax.swing.*; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.io.IOException; + +import static com.javarush.morozov.ui.handlers.MessageHandler.showErrorDialog; + +public class LoadFileButtonListener implements ActionListener { + private final JTextArea inputTextArea; + private final JTextArea outputTextArea; + private final FileHandler handler; + + public LoadFileButtonListener(JTextArea inputTextArea, JTextArea outputTextArea, FileHandler handler) { + this.inputTextArea = inputTextArea; + this.outputTextArea = outputTextArea; + this.handler = handler; + } + + @Override + public void actionPerformed(ActionEvent e) { + JFileChooser fileChooser = new JFileChooser(); + fileChooser.setFileFilter(FileHandler.getTextFileFilter()); + + int returnValue = fileChooser.showOpenDialog(null); + + if (returnValue == JFileChooser.APPROVE_OPTION) { + try { + String content = handler.readTextFile(fileChooser.getSelectedFile()); + outputTextArea.setText(null); + inputTextArea.setText(content); + } catch (SecurityException ex) { + showErrorDialog("Ошибка безопасности: " + ex.getMessage()); + } catch (IOException ex) { + showErrorDialog("Ошибка при чтении файла: " + ex.getMessage()); + } catch (Exception ex) { + showErrorDialog("Неожиданная ошибка: " + ex.getMessage()); + } + } + } +} diff --git a/src/main/java/com/javarush/morozov/ui/listeners/SaveFileButtonListener.java b/src/main/java/com/javarush/morozov/ui/listeners/SaveFileButtonListener.java new file mode 100644 index 0000000..f1e73fc --- /dev/null +++ b/src/main/java/com/javarush/morozov/ui/listeners/SaveFileButtonListener.java @@ -0,0 +1,50 @@ +package com.javarush.morozov.ui.listeners; + +import com.javarush.morozov.core.FileHandler; +import com.javarush.morozov.ui.handlers.MessageHandler; + +import javax.swing.*; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.io.File; +import java.io.IOException; + +public class SaveFileButtonListener implements ActionListener { + private final JTextArea outputTextArea; + private final FileHandler handler; + + public SaveFileButtonListener(JTextArea outputTextArea, FileHandler handler) { + this.outputTextArea = outputTextArea; + this.handler = handler; + } + + @Override + public void actionPerformed(ActionEvent e) { + if (outputTextArea == null || outputTextArea.getText().trim().isEmpty()) { + MessageHandler.showErrorDialog("Нет данных для сохранения"); + return; + } + + JFileChooser fileChooser = new JFileChooser(); + fileChooser.setFileFilter(FileHandler.getTextFileFilter()); + fileChooser.setDialogTitle("Сохранить файл"); + + int userFileSelection = fileChooser.showSaveDialog(null); + + if (userFileSelection == JFileChooser.APPROVE_OPTION) { + try { + File fileToSave = fileChooser.getSelectedFile(); + + if (!fileToSave.getName().toLowerCase().endsWith(".txt")) { + fileToSave = new File(fileToSave.getAbsolutePath() + ".txt"); + } + + handler.saveTextFile(fileToSave, outputTextArea.getText()); + + MessageHandler.showSuccessDialog("Файл успешно сохранён по пути: " + fileToSave.getPath()); + } catch (IOException ex) { + MessageHandler.showErrorDialog("Ошибка при сохранении: " + ex.getMessage()); + } + } + } +}