//CipherStreamJava/src/main/java/com/mattrixwv/cipherstream/monosubstitution/Caesar.java
//Matthew Ellison
// Created: 07-25-21
//Modified: 08-11-24
/*
Copyright (C) 2024 Mattrixwv
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with this program. If not, see
* The Caesar cipher is a substitution cipher where each letter in the * plaintext is shifted a fixed number of places down or up the alphabet. * This class allows you to encode and decode strings with options to preserve * capitalization, whitespace, and symbols. *
*/ public class Caesar{ private static final Logger logger = LoggerFactory.getLogger(Caesar.class); //?Fields /** The string that needs encoded/decoded */ protected String inputString; /** The encoded/decoded string */ protected String outputString; /** The amount that you need to shift each letter */ protected int shift; //?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; /** * Sets the shift amount and ensures it is within the proper bounds (0-25). * * @param shiftAmount the amount to shift each letter */ protected void setShift(int shiftAmount){ logger.debug("Setting shift {}", shiftAmount); //If you shift more than 26 you will just be wrapping back around again shift = shiftAmount % 26; logger.debug("Cleaned shift {}", shift); } /** * 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 */ protected void setInputString(String inputString) throws InvalidInputException{ if(inputString == null){ throw new InvalidInputException("Input cannot be null"); } logger.debug("Original input string '{}'", inputString); if(!preserveCapitals){ logger.debug("Removing case"); inputString = inputString.toLowerCase(); } if(!preserveWhitespace){ logger.debug("Removing whitespace"); inputString = inputString.replaceAll("\\s", ""); } if(!preserveSymbols){ logger.debug("Removing symbols"); inputString = inputString.replaceAll("[^a-zA-Z\\s]", ""); } logger.debug("Cleaned input string '{}'", inputString); this.inputString = inputString; if(this.inputString.isBlank()){ throw new InvalidInputException("Input must contain at least 1 letter"); } } /** * Encodes the input string by shifting letters according to the Caesar cipher. */ protected void encode(){ logger.debug("Encoding"); StringBuilder output = new StringBuilder(); for(int cnt = 0;cnt < inputString.length();++cnt){ char currentChar = inputString.charAt(cnt); //A temperary holder for the current working character logger.debug("Working character {}", currentChar); //If it is an upper case letter shift it and wrap if necessary if(Character.isUpperCase(currentChar)){ logger.debug("Encoding uppercase"); currentChar += shift; //Wrap around if the letter is now out of bounds if(currentChar < 'A'){ logger.debug("Wrapping around to Z"); currentChar += 26; } else if(currentChar > 'Z'){ logger.debug("Wrapping around to A"); currentChar -= 26; } } //If it is a lower case letter shift it and wrap if necessary else if(Character.isLowerCase(currentChar)){ logger.debug("Encoding lowercase"); currentChar += shift; //Wrap around if the letter is now out of bounds if(currentChar < 'a'){ logger.debug("Wrapping around to z"); currentChar += 26; } else if(currentChar > 'z'){ logger.debug("Wrapping around to a"); currentChar -= 26; } } //If it is whitespace, number, or punctuation just let it pass through //Add it to the output string logger.debug("Encoded character {}", currentChar); output.append(currentChar); } outputString = output.toString(); logger.debug("Saving encoded string '{}'", outputString); } /** * Decodes the input string by reversing the shift applied during encoding. */ protected void decode(){ logger.debug("Decoding"); StringBuilder output = new StringBuilder(); for(int cnt = 0;cnt < inputString.length();++cnt){ char currentChar = inputString.charAt(cnt); //A temperary holder for the current working character logger.debug("Working character {}", currentChar); //If it is an upper case letter shift it and wrap if necessary if(Character.isUpperCase(currentChar)){ logger.debug("Decoding uppercase"); currentChar -= shift; //Wrap around if the letter is now out of bounds if(currentChar < 'A'){ logger.debug("Wrapping around to Z"); currentChar += 26; } else if(currentChar > 'Z'){ logger.debug("Wrapping around to A"); currentChar -= 26; } } //If it is a lower case letter shift it and wrap if necessary else if(Character.isLowerCase(currentChar)){ logger.debug("Decoding lowercase"); currentChar -= shift; //Wrap around if the letter is now out of bounds if(currentChar < 'a'){ logger.debug("Wrapping around to z"); currentChar += 26; } else if(currentChar > 'z'){ logger.debug("Wrapping around to a"); currentChar -= 26; } } //If it is whitespace, number, or punctuation just let it pass through //Add it to the output string logger.debug("Decoded character {}", currentChar); output.append(currentChar); } outputString = output.toString(); logger.debug("Saving decoded string '{}'", outputString); } //?Constructor /** * Constructs a new {@code Caesar} instance with default settings. */ public Caesar(){ reset(); preserveCapitals = false; preserveWhitespace = false; preserveSymbols = false; } /** * Constructs a new {@code Caesar} 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 Caesar(boolean preserveCapitals, boolean preserveWhitespace, boolean preserveSymbols){ reset(); this.preserveCapitals = preserveCapitals; this.preserveWhitespace = preserveWhitespace; this.preserveSymbols = preserveSymbols; } /** * Encodes the input string with the specified shift amount. * * @param shiftAmount the amount to shift each letter * @param inputString the string to be encoded * @return the encoded string * @throws InvalidInputException if the input string is invalid */ public String encode(int shiftAmount, String inputString) throws InvalidInputException{ reset(); setShift(shiftAmount); setInputString(inputString); encode(); return outputString; } /** * Decodes the input string with the specified shift amount. * * @param shiftAmount the amount to shift each letter * @param inputString the string to be decoded * @return the decoded string * @throws InvalidInputException if the input string is invalid */ public String decode(int shiftAmount, String inputString) throws InvalidInputException{ reset(); setShift(shiftAmount); setInputString(inputString); decode(); return outputString; } //?Getters /** * Gets the current input string. * * @return the input string */ public String getInputString(){ return inputString; } /** * Gets the current shift amount. * * @return the shift amount */ public int getShift(){ return shift; } /** * Gets the current output string. * * @return the output string */ public String getOutputString(){ return outputString; } /** * Resets the internal fields to their default values. */ public void reset(){ logger.debug("Resetting fields"); inputString = ""; outputString = ""; shift = 0; } }