如何在Java MVC Swing应用程序中轻松处理多个ActionListener?

时间:2017-12-25 22:48:01

标签: java swing model-view-controller

我编写了一个程序,解码给定字符频率和文本列表的文件进行解码(与问题无关,只是给出一些上下文)。该应用程序使用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);
    }
}

0 个答案:

没有答案