//CipherStreamJava/src/main/java/com/mattrixwv/CipherStreamJava/monoSubstitution/Vigenere.java //Matthew Ellison // Created: 07-25-21 //Modified: 02-22-22 package com.mattrixwv.cipherstream.monosubstitution; import java.util.ArrayList; import java.util.List; import com.mattrixwv.cipherstream.exceptions.InvalidInputException; import com.mattrixwv.cipherstream.exceptions.InvalidKeywordException; public class Vigenere{ protected String inputString; //This is the string that needs encoded/decoded protected String outputString; //This is the string that is output after encoding/decoding protected String keyword; //This is the keyword that is resposible for determining the offsets that you change each character by protected ArrayList offset; //Holds the offsets coputed from each character in the keyword protected boolean preserveCapitals; //Whether to respect capitals in the output string protected boolean preserveWhitespace; //Whether to respect whitespace in the output string protected boolean preserveSymbols; //Whether to respect symbols in the output string //Uses keyword to calculate the offset for the Caesar cipher for each character protected void setOffset(){ //Reserve the correct size to increase speed later offset.ensureCapacity(keyword.length()); //Loop through every letter in keyword and get the offset from A for(int cnt = 0;cnt < keyword.length();++cnt){ char letter = keyword.charAt(cnt); offset.add((letter - 'A') % 26); } } //Sets inputString protected void setInputString(String inputString) throws InvalidInputException{ if(inputString == null){ throw new NullPointerException("Input cannot be null"); } if(!preserveCapitals){ inputString = inputString.toUpperCase(); } if(!preserveWhitespace){ inputString = inputString.replaceAll("\\s", ""); } if(!preserveSymbols){ inputString = inputString.replaceAll("[^a-zA-Z\\s]", ""); } this.inputString = inputString; if(this.inputString.isBlank()){ throw new InvalidInputException("Input must contain at least 1 letter"); } } //Sets keyword protected void setKeyword(String keyword) throws InvalidKeywordException{ if(keyword == null){ throw new NullPointerException("Keyword cannot be null"); } //Convert all letters to uppercase keyword = keyword.toUpperCase(); //Remove all characters except capital letters keyword = keyword.replaceAll("[^A-Z]", ""); //Save the string this.keyword = keyword; //Make sure offset is empty before adding to it offset.clear(); setOffset(); //If after all the eliminating of unusable characters the keyword is empty throw an exception if(this.keyword.isBlank()){ throw new InvalidKeywordException("Keyword must contain at least 2 letters"); } } //Encodes inputString and stores the result in outputString protected String encode(){ StringBuilder output = new StringBuilder(); //Step through every character in the inputString and advance it the correct amount, according to offset int offsetCnt = 0; for(int inputCnt = 0;inputCnt < inputString.length();++inputCnt){ char letter = inputString.charAt(inputCnt); if(Character.isUpperCase(letter)){ letter += offset.get((offsetCnt++) % offset.size()); //Make sure the character is still a letter, if not, wrap around if(letter < 'A'){ letter += 26; } else if(letter > 'Z'){ letter -= 26; } } else if(Character.isLowerCase(letter)){ letter += offset.get((offsetCnt++) % offset.size()); //Make sure the character is still a letter, if not, wrap around if(letter < 'a'){ letter += 26; } else if(letter > 'z'){ letter -= 26; } } output.append(letter); } outputString = output.toString(); return outputString; } //Decodes inputString and stores the result in outputString protected String decode(){ StringBuilder output = new StringBuilder(); //Step through every character in the inputString and advance it the correct amount, according to offset int offsetCnt = 0; for(int letterCnt = 0;letterCnt < inputString.length();++letterCnt){ char letter = inputString.charAt(letterCnt); if(Character.isUpperCase(letter)){ letter -= offset.get((offsetCnt++) % offset.size()); if(letter < 'A'){ letter += 26; } else if(letter > 'Z'){ letter -= 26; } } else if(Character.isLowerCase(letter)){ letter -= offset.get((offsetCnt++) % offset.size()); if(letter < 'a'){ letter += 26; } else if(letter > 'z'){ letter -= 26; } } output.append(letter); } outputString = output.toString(); return outputString; } //Constructor public Vigenere(){ offset = new ArrayList<>(); reset(); preserveCapitals = false; preserveWhitespace = false; preserveSymbols = false; } public Vigenere(boolean preserveCapitals, boolean preserveWhitespace, boolean preserveSymbols){ offset = new ArrayList<>(); reset(); this.preserveCapitals = preserveCapitals; this.preserveWhitespace = preserveWhitespace; this.preserveSymbols = preserveSymbols; } //Returns the current inputString public String getInputString(){ return inputString; } //Returns the current outputString public String getOutputString(){ return outputString; } //Returns the current keyword public String getKeyword(){ return keyword; } //Returns the current offsets (Used mostly in bug fixing) public List getOffsets(){ return offset; } //Encodes input using key and returns the result public String encode(String keyword, String inputString) throws InvalidKeywordException, InvalidInputException{ reset(); setKeyword(keyword); setInputString(inputString); return encode(); } //Decodes input using key and returns the result public String decode(String keyword, String inputString) throws InvalidKeywordException, InvalidInputException{ reset(); setKeyword(keyword); setInputString(inputString); return decode(); } //Makes sure all of the variables are empty public void reset(){ inputString = ""; outputString = ""; keyword = ""; offset.clear(); } }