package com.mattrixwv.cipherstream.monosubstitution; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.mattrixwv.cipherstream.exceptions.InvalidInputException; import com.mattrixwv.cipherstream.exceptions.InvalidKeywordException; /** * A class for encoding and decoding strings using the Beaufort cipher, * which is a variant of the Vigenère cipher with additional steps. * *

* The Beaufort cipher consists of three main steps: *

* * This class allows you to encode and decode strings with options to preserve * capitalization, whitespace, and symbols. */ public final class Beaufort{ private static final Logger logger = LoggerFactory.getLogger(Beaufort.class); //?Fields /** This is the string that needs encoded/decoded */ protected String inputString; /** This is the string that is output after encoding/decoding */ protected String outputString; /** This is the keyword that is responsible for determining the offsets that you change each character by */ protected String keyword; //?Settings /** Persist capitals in the output string */ protected boolean preserveCapitals; /** Persist whitespace in the output string */ protected boolean preserveWhitespace; /** Persist symbols in the output string */ protected boolean preserveSymbols; //?Internal ciphers /** The first step in encoding/decoding the cipher */ protected Atbash atbash; /** The second step in encoding/decoding the cipher */ protected Caesar caesar; /** The third step in encoding/decoding the cipher */ protected Vigenere vigenere; /** * Sets the input string for encoding or decoding, applying removal options * for case, whitespace, and symbols. * * @param inputString the string to be processed * @throws InvalidInputException if the input string is null or blank after processing */ public void setInputString(String inputString) throws InvalidInputException{ //Make sure the input isn't null if(inputString == null){ throw new InvalidInputException("Input cannot be null"); } logger.debug("Original input string '{}'", inputString); //Apply removal options if(!preserveCapitals){ logger.debug("Removing case"); inputString = inputString.toUpperCase(); } if(!preserveWhitespace){ logger.debug("Removing whitespace"); inputString = inputString.replaceAll("\\s", ""); } if(!preserveSymbols){ logger.debug("Removing symbols"); inputString = inputString.replaceAll("[^a-zA-Z\\s]", ""); } //Save the string logger.debug("Cleaned input string '{}'", inputString); this.inputString = inputString; //Make sure the string isn't blank if(this.inputString.isBlank()){ throw new InvalidInputException("Input must contain at least 1 letter"); } } /** * Sets the keyword for encoding or decoding, ensuring it contains only * uppercase letters and is at least 2 letters long. * * @param keyword the keyword for the Vigenère cipher * @throws InvalidKeywordException if the keyword is null, blank, or less than 2 letters */ public void setKeyword(String keyword) throws InvalidKeywordException{ //Make sure the keyword isn't null if(keyword == null){ throw new InvalidKeywordException("Keyword cannot be null"); } logger.debug("Original keyword '{}'", keyword); //Convert all letters to uppercase logger.debug("Removing case"); keyword = keyword.toUpperCase(); //Remove all characters except capital letters logger.debug("Removing all non-letters"); keyword = keyword.replaceAll("[^A-Z]", ""); //Save the string logger.debug("Cleaned keyword '{}'", keyword); this.keyword = keyword; //If after all the elimination of unusable characters the keyword is empty throw an exception if(this.keyword.isBlank() || (this.keyword.length() < 2)){ throw new InvalidKeywordException("Keyword must contain at least 2 letters"); } } /** * Encodes the input string using the Beaufort cipher and stores the result in {@code outputString}. * * @throws InvalidKeywordException if the keyword is invalid * @throws InvalidInputException if the input string is invalid */ protected void encode() throws InvalidKeywordException, InvalidInputException{ logger.debug("Encoding"); code(); } /** * Decodes the input string using the Beaufort cipher and stores the result in {@code outputString}. * Decoding is the same process as encoding in this cipher. * * @throws InvalidKeywordException if the keyword is invalid * @throws InvalidInputException if the input string is invalid */ protected void decode() throws InvalidKeywordException, InvalidInputException{ logger.debug("Decoding"); //Decoding is just encoding again code(); } /** * Performs the Beaufort cipher encoding/decoding process: * */ protected void code(){ //Reverse the string logger.debug("Encoding with Atbash"); String atbashString = atbash.encode(inputString); //Shift the reversal by 1 //?Not quite sure why this is needed. Need to look into this cipher a bit more closely logger.debug("Shifting all letters by 1"); String caesarString = caesar.encode(1, atbashString); //Shift each letter according to the key logger.debug("Encoding with Vigenere"); String vigenereString = vigenere.encode(keyword, caesarString); //Save the output logger.debug("Saving output string '{}'", vigenereString); this.outputString = vigenereString; } //?Constructor /** * Constructs a new {@code Beaufort} instance with default settings. */ public Beaufort(){ preserveCapitals = false; preserveWhitespace = false; preserveSymbols = false; atbash = new Atbash(false, false, false); caesar = new Caesar(false, false, false); vigenere = new Vigenere(false, false, false); reset(); } /** * Constructs a new {@code Beaufort} instance with specified settings. * * @param preserveCapitals whether to preserve capitalization in the output * @param preserveWhitespace whether to preserve whitespace in the output * @param preserveSymbols whether to preserve symbols in the output */ public Beaufort(boolean preserveCapitals, boolean preserveWhitespace, boolean preserveSymbols){ this.preserveCapitals = preserveCapitals; this.preserveWhitespace = preserveWhitespace; this.preserveSymbols = preserveSymbols; atbash = new Atbash(preserveCapitals, preserveWhitespace, preserveSymbols); caesar = new Caesar(preserveCapitals, preserveWhitespace, preserveSymbols); vigenere = new Vigenere(preserveCapitals, preserveWhitespace, preserveSymbols); reset(); } /** * Encodes the input string using the specified keyword. * * @param keyword the keyword for the Vigenère cipher * @param inputString the string to be encoded * @return the encoded string * @throws InvalidKeywordException if the keyword is invalid * @throws InvalidInputException if the input string is invalid */ public String encode(String keyword, String inputString) throws InvalidKeywordException, InvalidInputException{ //Set the parameters setKeyword(keyword); setInputString(inputString); //Encode and return the message encode(); return outputString; } /** * Decodes the input string using the specified keyword. * * @param keyword the keyword for the Vigenère cipher * @param inputString the string to be decoded * @return the decoded string * @throws InvalidKeywordException if the keyword is invalid * @throws InvalidInputException if the input string is invalid */ public String decode(String keyword, String inputString) throws InvalidKeywordException, InvalidInputException{ //Set the parameters setKeyword(keyword); setInputString(inputString); //Decode and return the message decode(); return outputString; } //?Getters /** * Gets the current input string. * * @return the input string */ public String getInputString(){ return inputString; } /** * Gets the current output string. * * @return the output string */ public String getOutputString(){ return outputString; } /** * Gets the current keyword. * * @return the keyword */ public String getKeyword(){ return keyword; } /** * Resets the input string, output string, and keyword to empty. */ public void reset(){ logger.debug("Resetting fields"); inputString = ""; outputString = ""; keyword = ""; } }