我编写了一个程序,解码给定字符频率和文本列表的文件进行解码(与问题无关,只是给出一些上下文)。该应用程序使用4个按钮,但是我目前的方式是在我的视图中(这个应用程序遵循MVC模型)我有添加动作侦听器的方法,然后在控制器中调用它们给每个人一个嵌套类它充当ActionListener。这是有效的,但是我的控制器基本上是复制粘贴的代码,我觉得有一种方法可以更简洁地完成这项工作。我已将我的代码包含在下面,感谢所有输入。
View.java:
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.FlowLayout;
import java.awt.Dimension;
import javax.swing.JButton;
import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JTextArea;
import java.io.File;
public class View
{
//Declare variables
private JFrame frame;
private JPanel mainPanel;
private JTextArea instructions;
private JLabel frequenciesFileLoadedLabel;
private JLabel cipherFileLoadedLabel;
private JButton frequenciesFileLoadButton;
private JButton cipherFileLoadButton;
private JButton decipherByRankButton;
private JButton decipherByNearestFrequencyButton;
private boolean cipherLoaded;
private boolean frequenciesLoaded;
//Constructor
public View()
{
//Set flags for whether files have been loaded to false
cipherLoaded = false;
frequenciesLoaded = false;
//Set up the main frame
frame = new JFrame("Text Decrypter");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setResizable(false);
//Set up the main panel (and the only panel being used)
mainPanel = new JPanel(new FlowLayout(FlowLayout.CENTER, 20, 10));
mainPanel.setPreferredSize(new Dimension(100, 290));
//Set up the instructions that will be displayed on the top of the program
instructions = new JTextArea("Instructions: upload a file with encrypted text you want to \ndecipher and a file with character frequencies. Once \nboth files are uploaded, you will be able to decipher \nthem by pressing the relevant buttons.", 4, 4);
instructions.setEditable(false);
//Set up labels for whether files have been loaded
frequenciesFileLoadedLabel = new JLabel("No file with character frequencies loaded");
cipherFileLoadedLabel = new JLabel("No file to decipher loaded");
//Set up buttons for loading files
frequenciesFileLoadButton = new JButton("Load character frequencies file");
cipherFileLoadButton = new JButton("Load file to decipher");
//Set up buttons for decrypting files and set them to disabled initially
decipherByRankButton = new JButton("Decipher file by rank of character frequencies");
decipherByRankButton.setEnabled(false);
decipherByNearestFrequencyButton = new JButton("Decipher file by the nearest character frequency");
decipherByNearestFrequencyButton.setEnabled(false);
//Add all elements of the GUI to the main panel
mainPanel.add(instructions);
mainPanel.add(frequenciesFileLoadButton);
mainPanel.add(frequenciesFileLoadedLabel);
mainPanel.add(cipherFileLoadButton);
mainPanel.add(cipherFileLoadedLabel);
mainPanel.add(decipherByRankButton);
mainPanel.add(decipherByNearestFrequencyButton);
//Finalise and display the frame
frame.add(mainPanel);
frame.pack();
frame.setVisible(true);
}
//Method to check whether the buttons used for decrypting can be pressed
private void enableButtons()
{
//If both files are loaded, enable the buttons, otherwise disable both
if (cipherLoaded == true && frequenciesLoaded == true)
{
decipherByRankButton.setEnabled(true);
decipherByNearestFrequencyButton.setEnabled(true);
}
else{
decipherByRankButton.setEnabled(false);
decipherByNearestFrequencyButton.setEnabled(false);
}
}
//Method used for loading files from a file chooser
private File loadFile()
{
//Get the user to choose a file
JFileChooser fileChooser = new JFileChooser();
fileChooser.setCurrentDirectory(new File( System.getProperty("user.dir")));
int fileChooserResult = fileChooser.showOpenDialog(frame);
//If a file has been chosen, return that file. Otherwise display an error
if (fileChooserResult == JFileChooser.APPROVE_OPTION)
{
return fileChooser.getSelectedFile();
}
else
{
JOptionPane.showMessageDialog(frame, "Error loading file. Please try again.", "Error", JOptionPane.ERROR_MESSAGE);
return null;
}
}
//Method for loading the character frequencies file
public File loadFrequencies()
{
//Load the file
File loadedFile = loadFile();
//If no file has been loaded, set the relevant flags to false and disable the decypting buttons
if (loadedFile == null)
{
frequenciesLoaded = false;
enableButtons();
frequenciesFileLoadedLabel.setText("No file with character frequencies loaded");
return loadedFile;
}
//If a file has been loaded, set the relevant flags to true and enable the decrypting buttons (if the other file has been loaded too)
else
{
frequenciesLoaded = true;
enableButtons();
frequenciesFileLoadedLabel.setText("Character frequencies file loaded");
return loadedFile;
}
}
//Method for loading the ciphered file
public File loadCiphered()
{
File loadedFile = loadFile();
//If no file has been loaded, set the relevant flags to false and disable the decypting buttons
if (loadedFile == null)
{
cipherLoaded = false;
enableButtons();
cipherFileLoadedLabel.setText("No file with text to decipher loaded");
return loadedFile;
}
//If a file has been loaded, set the relevant flags to true and enable the decrypting buttons (if the other file has been loaded too)
else
{
cipherLoaded = true;
enableButtons();
cipherFileLoadedLabel.setText("File with text to decipher loaded");
return loadedFile;
}
}
//Method used to display errors to the user
public void displayError(String errorString)
{
JOptionPane.showMessageDialog(frame, errorString, "Error", JOptionPane.ERROR_MESSAGE);
}
//Method that displays a message when a file has been output
public void fileOutput()
{
JOptionPane.showMessageDialog(frame, "File deciphered and output to file \"output.txt\".");
}
//Add an actionlistener for every button in the program
public void addFrequencyButtonListener(ActionListener listenForFreqButton)
{
frequenciesFileLoadButton.addActionListener(listenForFreqButton);
}
public void addCipherButtonListener(ActionListener listenForCipherButton)
{
cipherFileLoadButton.addActionListener(listenForCipherButton);
}
public void addDecipherRankButtonListener(ActionListener listenForDecipherRankButton)
{
decipherByRankButton.addActionListener(listenForDecipherRankButton);
}
public void addDecipherFrequencyButtonListener(ActionListener listenForDecipherFrequencyButton)
{
decipherByNearestFrequencyButton.addActionListener(listenForDecipherFrequencyButton);
}
}
Controller.java:
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
public class Controller
{
//Variables that will hold the view and controller.
private View view;
private Model model;
//The constructor
public Controller(View view, Model model)
{
//Set the view and model
this.view = view;
this.model = model;
//Set an actionlistener for every button
this.view.addCipherButtonListener(new CipherButtonListener());
this.view.addFrequencyButtonListener(new FrequencyButtonListener());
this.view.addDecipherRankButtonListener(new DecipherRankButtonListener());
this.view.addDecipherFrequencyButtonListener(new DecipherFrequencyButtonListener());
}
//Set up a listener for every button
class CipherButtonListener implements ActionListener
{
public void actionPerformed(ActionEvent e)
{
//Call the method in model to load the ciphered file when the relevant button is clicked
model.loadCipherered(view.loadCiphered());
}
}
class FrequencyButtonListener implements ActionListener
{
public void actionPerformed(ActionEvent e)
{
//Call the method in the model to load the frequencies file when the relevant button is clicked
model.loadFrequencies(view.loadFrequencies());
}
}
class DecipherRankButtonListener implements ActionListener
{
public void actionPerformed(ActionEvent e)
{
//Decipher the file by rank and display a message
model.decipherByRank();
view.fileOutput();
}
}
class DecipherFrequencyButtonListener implements ActionListener
{
public void actionPerformed(ActionEvent e)
{
//Decipher the file by frequency and display a message
model.decipherByFrequency();
view.fileOutput();
}
}
}
Model.java:
import java.util.Map;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Scanner;
import java.util.Collections;
import java.util.ArrayList;
import java.util.Iterator;
import java.io.*;
public class Model
{
//Constant for the amount of letters in the alphabet
private final int ALPHABET_LENGTH = 26;
//HashMaps for storing the character frequencies in the frequency and cipher files
private LinkedHashMap<Character, Float> sortedEncryptedMap;
private LinkedHashMap<Character, Float> frequenciesMap;
//Array holding the whole alphabet - used when loading files
private char[] alphabet;
//String which holds the encrypted text
private static String encryptedFileString;
//Enum used in the sortHashMap method to specify what hashmap to write to
private enum ImportType {
FREQUENCY, CIPHER
}
//The constructor
public Model()
{
//Initialise all variables
sortedEncryptedMap = new LinkedHashMap<>();
alphabet = "abcdefghijklmnopqrstuvwxyz".toCharArray();
encryptedFileString = "";
frequenciesMap = new LinkedHashMap <>();
}
//Method that orders hashmaps in numerical order. ImportType necessary to check which hashmap to write to
public void sortHashMap(HashMap<Character, Float> inputMap, ImportType importType)
{
//Set the keys and values to separate arraylists
ArrayList<Character> mapKeys = new ArrayList<>(inputMap.keySet());
ArrayList<Float> mapValues = new ArrayList<>(inputMap.values());
//Sort the extracted keys and values
Collections.sort(mapValues, Collections.reverseOrder());
Collections.sort(mapKeys, Collections.reverseOrder());
//Renitialise the hashmaps to store new data
if (importType == ImportType.FREQUENCY)
{
frequenciesMap = new LinkedHashMap<>();
}
else if (importType == ImportType.CIPHER)
{
sortedEncryptedMap = new LinkedHashMap<>();
}
//Set up an iterator that will go over all the map values
Iterator<Float> valueIterator = mapValues.iterator();
//Double while loop that will terminate after the last value is reached
while (valueIterator.hasNext()) {
Float currentValue = valueIterator.next();
Iterator<Character> keyIterator = mapKeys.iterator();
while (keyIterator.hasNext()) {
Character currentKey = keyIterator.next();
//Check if key and value match
Float comp1 = inputMap.get(currentKey);
Float comp2 = currentValue;
if (comp1 == comp2) {
keyIterator.remove();
//Put new found value into the right place according to the sorted arraylists
if (importType == ImportType.FREQUENCY)
{
frequenciesMap.put(currentKey, currentValue);
}
else if (importType == ImportType.CIPHER)
{
sortedEncryptedMap.put(currentKey, currentValue);
}
break;
}
}
}
}
//Method for loading the frequencies file
public void loadFrequencies(File inputFile)
{
try
{
//Set up a scanner and input the loaded file into it
Scanner scanner = new Scanner (inputFile);
//Clear the hashmap for storing new frequencies
frequenciesMap = new LinkedHashMap <Character, Float>();
//Temporary hashmap for unsorted input
LinkedHashMap<Character, Float> tempMap = new LinkedHashMap <Character, Float>();
//Keep reading the file until th end
while (scanner.hasNextLine())
{
//Store the line currently being checked
String nextLine = scanner.nextLine();
//Separate the letter in the string
Character currentLetter = nextLine.charAt(0);
//Separate the frequency from the string
float currentValue = Float.parseFloat(nextLine.substring(1, nextLine.length()));
//Store the frequency and letter in the temporary hashmap
tempMap.put(currentLetter, currentValue);
}
//Sort the hashmap and store it in a variable outside of this method
sortHashMap(tempMap, ImportType.FREQUENCY);
}
catch (IOException e)
{
//Print stack trace if there's an error
e.printStackTrace();
}
}
//Method for loading the ciphered text file
public void loadCipherered(File inputFile)
{
try {
//Input the text from the ciphered file into a string
if (inputFile == null)
{
return;
}
//Set up a scanner and input the loaded file into it
Scanner scanner = new Scanner (inputFile);
//Clear the string which holds the input file and the hashmap that will hold the letter frequencies
sortedEncryptedMap = new LinkedHashMap<>();
encryptedFileString = "";
//Flag to check whether it's the first line of the file that's being checked.
boolean firstLine = true;
while (scanner.hasNextLine())
{
//If it's not the first line add a newline character (due to the nature of scanner.nextLine, this is necessary to preserve new line)
if (firstLine == false)
{
encryptedFileString += "\n";
}
//Add text from the file to the stirng
encryptedFileString += scanner.nextLine();
firstLine = false;
}
//Ignore cases
encryptedFileString = encryptedFileString.toLowerCase();
//When the file has been loaded, count the occurances of each character and assign them to a HashMap
Map<Character, Integer> occuranceMap = new HashMap <Character, Integer>();
//Populate the hashmap with the alphabet and initially, set each count to 0
for(int i = 0; i< ALPHABET_LENGTH; i++)
{
occuranceMap.put(alphabet[i], 0);
}
//Iterate through the encrypted file string and count the amount of times each letter occurs
for (int i = 0; i < encryptedFileString.length(); i++)
{
for (int j = 0; j < ALPHABET_LENGTH; j++)
{
if (alphabet[j] == encryptedFileString.charAt(i) )
{
occuranceMap.put(alphabet[j], occuranceMap.get(alphabet[j]) + 1);
}
}
}
//Caclulate the total number of letters (excluding other characters) in the encrypted file
int totalLetters = 0;
for (int i = 0; i < ALPHABET_LENGTH; i++)
{
totalLetters += occuranceMap.get(alphabet[i]);
}
//Make a hashmap that contains the percentages of each letter
LinkedHashMap<Character, Float> percentageMap = new LinkedHashMap <Character, Float>();
for (int i = 0; i < ALPHABET_LENGTH; i++)
{
percentageMap.put(alphabet[i], ((float)occuranceMap.get(alphabet[i]) / (float)totalLetters) * 100 );
}
//Sort the hashmap with percentages as values in order of highest percentage occurance
sortHashMap(percentageMap, ImportType.CIPHER);
}
catch (IOException e)
{
//Print stack trace if there's an error
e.printStackTrace();
}
}
//Method for deciphering the text purely by the rank of each character
public void decipherByRank()
{
try
{
//Set up a filewriter to write to a file
FileWriter fileWriter = new FileWriter("output.txt");
//Split up the keys from each of the character frequencies hashmaps.
ArrayList<Character> orderedCharacterFrequencies = new ArrayList<Character>(frequenciesMap.keySet());
ArrayList<Character> orderedEncryptedFrequencies = new ArrayList<Character>(sortedEncryptedMap.keySet());
//Declare a stringwriter to write the output string as it's much faster than writing to a string
StringWriter outputWriter = new StringWriter();
//Iterate through the encrypted string, checking every character
for (int i = 0; i < encryptedFileString.length(); i++)
{
//Flag for checking whether the letter has been replaced or not - whether it's a letter or another character
boolean replaced = false;
//If the character being checked is a letter, replace it with a letter from the frequencies hashmap at the same rank
for (int j = 0; j < ALPHABET_LENGTH; j++)
{
if (encryptedFileString.charAt(i) == orderedEncryptedFrequencies.get(j))
{
outputWriter.write(orderedCharacterFrequencies.get(j));
replaced = true;
}
}
//If the character hasnt been found in the arraylists, is not a letter and therefore should be kept the same
if (replaced == false)
{
outputWriter.write(encryptedFileString.charAt(i));
}
}
//Write the string to a file
fileWriter.write(outputWriter.toString());
//Close the filewriter
fileWriter.close();
}
catch (IOException e)
{
//Print stack trace if there's an error
e.printStackTrace();
}
}
//Method for deciphering the encyrpted text by the nearest frequency
public void decipherByFrequency()
{
try
{
//Set up a filewriter to write to a file
FileWriter fileWriter = new FileWriter("output.txt");
//Split up the keys from each of the character frequencies hashmaps
ArrayList<Character> orderedCharacterFrequencyKeys = new ArrayList<Character>(frequenciesMap.keySet());
ArrayList<Character> orderedEncryptedFrequencyKeys = new ArrayList<Character>(sortedEncryptedMap.keySet());
//Split up the values from each of the character frequency hashmaps
ArrayList<Float> orderedCharacterFrequencyValues = new ArrayList<Float>(frequenciesMap.values());
ArrayList<Float> orderedEncryptedFrequencyValues = new ArrayList<Float>(sortedEncryptedMap.values());
//Declare a stringwriter to write the output string as it's much faster than writing to a string
StringWriter outputWriter = new StringWriter();
//Iterate through the encrypted string, checking every character
for (int i = 0; i < encryptedFileString.length(); i++)
{
//Flag for checking whether the letter has been replaced or not - whether it's a letter or another character
boolean replaced = false;
//Check if the character is a letter
for (int j = 0; j < ALPHABET_LENGTH; j++)
{
if (encryptedFileString.charAt(i) == orderedEncryptedFrequencyKeys.get(j))
{
//Variable for storing the difference in percentages of any characters
float difference = 150.0f;
float currentCharacterPercentage = orderedEncryptedFrequencyValues.get(j);
//Index of currently the closest percentage
int closestCharacterIndex = 0;
//terate through other letters, checking which will give the lowest difference
for (int k = 0; k < ALPHABET_LENGTH; k++)
{
if ( Math.abs(currentCharacterPercentage - orderedCharacterFrequencyValues.get(k) ) < difference )
{
closestCharacterIndex = k;
difference = Math.abs(currentCharacterPercentage - orderedCharacterFrequencyValues.get(k));
}
}
//Replace the file of the lowest difference and write it to the stringwriter
outputWriter.write(orderedCharacterFrequencyKeys.get(closestCharacterIndex));
replaced = true;
}
}
//If the character hasnt been found in the arraylists, is not a letter and therefore should be kept the same
if (replaced == false)
{
outputWriter.write(encryptedFileString.charAt(i));
}
}
//Write the string to a file
fileWriter.write(outputWriter.toString());
//Close the filewriter
fileWriter.close();
}
catch (IOException e)
{
//Print stack trace if there's an error
e.printStackTrace();
}
}
}
Main.java:
public class Main {
public static void main(String[] args)
{
//Declare an instance of view and model and pass those to the controller
View mainView = new View();
Model mainModel = new Model();
Controller mainController = new Controller(mainView, mainModel);
}
}