//CipherStreamJava/src/main/java/com/mattrixwv/cipherstream/monosubstitution/BaseX.java
//Mattrixwv
// Created: 01-08-22
//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
* This class supports encoding and decoding of ASCII characters into their base-X representations, * where X is the base provided by the user. It ensures that input strings are valid and within the acceptable range * for the specified base. *
*/ public class BaseX{ private static final Logger logger = LoggerFactory.getLogger(BaseX.class); //?Fields /** The string that needs encoded/decoded */ protected String inputString; /** The encoded/decoded string */ protected String outputString; //?Settings /** The base that the number will be encoded at */ protected int base; /** * Sets the input string for encoding, ensuring it is not null and contains at least one letter. * * @param inputString the string to be encoded * @throws InvalidInputException if the input string is null or blank */ protected void setInputStringEncode(String inputString) throws InvalidInputException{ if(inputString == null){ throw new InvalidInputException("Input cannot be null"); } logger.debug("Setting input string for encoding '{}'", inputString); this.inputString = inputString; if(this.inputString.isBlank()){ throw new InvalidInputException("Input must contain at least 1 letter"); } } /** * Sets the input string for decoding, ensuring it is not null, does not contain invalid characters, and is properly formatted. * * @param inputString the string to be decoded * @throws InvalidCharacterException if the input string contains invalid characters * @throws InvalidInputException if the input string is null or blank */ protected void setInputStringDecode(String inputString) throws InvalidCharacterException, InvalidInputException{ if(inputString == null){ throw new InvalidInputException("Input cannot be null"); } logger.debug("Setting input string for decoding '{}'", inputString); //Create a string of valid 'numbers' logger.debug("Creating string of valid 'numbers'"); StringBuilder validNumbers = new StringBuilder(); for(int cnt = 0;cnt < base;++cnt){ String number = Integer.toString(cnt, base).toUpperCase(); logger.debug("Current number {}, converted {}", cnt, number); validNumbers.append(number); } //Remove all invalid characters logger.debug("Checking for invalid characters"); this.inputString = inputString.replaceAll("[^" + validNumbers.toString() + "\\s]", ""); //Throw an exception if there were any invalid characters if(!this.inputString.equals(inputString)){ throw new InvalidCharacterException("inputString cannot contain anything except numbers 0-" + Integer.toString(base - 1, base) + ", and whitespace"); } logger.debug("Cleaned input string '{}'", inputString); if(this.inputString.isBlank()){ throw new InvalidInputException("Input must contain at least 1 letter"); } } /** * Sets the base for encoding and decoding, ensuring it is within valid range. * * @param base the base to be set * @throws InvalidBaseException if the base is less than Character.MIN_RADIX or greater than Character.MAX_RADIX */ protected void setBase(int base) throws InvalidBaseException{ if(base < Character.MIN_RADIX){ throw new InvalidBaseException("Base cannot be less than " + Character.MIN_RADIX); } else if(base > Character.MAX_RADIX){ throw new InvalidBaseException("Base cannot be larger than " + Character.MAX_RADIX); } logger.debug("Setting base {}", base); this.base = base; } /** * Encodes the input string using the specified base. */ protected void encode(){ logger.debug("Encoding"); //Encode every character in inputString StringJoiner output = new StringJoiner(" "); for(int cnt = 0;cnt < inputString.length();++cnt){ //Get the next character char ch = inputString.charAt(cnt); logger.debug("Working number {}", ch); //Encode the character to binary and add it to the output String convertedNum = Integer.toString(ch, base); output.add(convertedNum); logger.debug("Converted number {}", convertedNum); } //Save the output outputString = output.toString().toUpperCase(); logger.debug("Saving output string '{}'", outputString); } /** * Decodes the input string from the specified base. * * @throws InvalidCharacterException if the input string contains invalid characters for the base */ protected void decode() throws InvalidCharacterException{ logger.debug("Decoding"); //Decode every binary number in the string StringBuilder output = new StringBuilder(); for(String baseXString : inputString.split(" ")){ logger.debug("Current number {}", baseXString); //Decode the current binary number int num = Integer.valueOf(baseXString, base); logger.debug("Decoded number {}", num); //Make sure it is in a valid range if((num < 0) || (num > 255)){ throw new InvalidCharacterException("The base" + base + " string '" + baseXString + "' is not a valid ASCII character"); } //Convert the int to a char and save it output.append((char)num); } //Save the output outputString = output.toString(); logger.debug("Saving output string '{}'", outputString); } //?Constructor /** * Constructs a new {@code BaseX} instance with the default base of 2. * * @throws InvalidBaseException if the default base is invalid */ public BaseX() throws InvalidBaseException{ reset(); setBase(2); } /** * Constructs a new {@code BaseX} instance with the specified base. * * @param base the base to be used for encoding and decoding * @throws InvalidBaseException if the base is invalid */ public BaseX(int base) throws InvalidBaseException{ reset(); setBase(base); } /** * Encodes the given input string using the current base. * * @param inputString the string to be encoded * @return the encoded string * @throws InvalidInputException if the input string is invalid */ public String encode(String inputString) throws InvalidInputException{ reset(); setInputStringEncode(inputString); encode(); return outputString; } /** * Encodes the given input string using the specified base. * * @param base the base to use for encoding * @param inputString the string to be encoded * @return the encoded string * @throws InvalidBaseException if the base is invalid * @throws InvalidInputException if the input string is invalid */ public String encode(int base, String inputString) throws InvalidBaseException, InvalidInputException{ reset(); setBase(base); setInputStringEncode(inputString); encode(); return outputString; } /** * Decodes the given input string using the current base. * * @param inputString the string to be decoded * @return the decoded string * @throws InvalidCharacterException if the input string contains invalid characters * @throws InvalidInputException if the input string is invalid */ public String decode(String inputString) throws InvalidCharacterException, InvalidInputException{ reset(); setInputStringDecode(inputString); decode(); return outputString; } /** * Decodes the given input string using the specified base. * * @param base the base to use for decoding * @param inputString the string to be decoded * @return the decoded string * @throws InvalidBaseException if the base is invalid * @throws InvalidCharacterException if the input string contains invalid characters * @throws InvalidInputException if the input string is invalid */ public String decode(int base, String inputString) throws InvalidBaseException, InvalidCharacterException, InvalidInputException{ reset(); setBase(base); setInputStringDecode(inputString); 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 base. * * @return the base */ public int getBase(){ return base; } /** * Resets the input and output strings to empty. */ public void reset(){ logger.debug("Resetting fields"); inputString = ""; outputString = ""; } }