ElearningWorld.org

For the online learning world

Elearning WorldLinuxTechnical

A little bit more Java

Introduction

As a follow on from ‘A little bit of Java’, I thought that we’d progress into something a bit more complicated. The thing is with software creation is that the initial learning curve is steep, but once you’ve gotten over that then things do become easier. One way of climbing that initial curve is to have a defined goal with an outcome that you strive to reach. The program also needs to have a purpose so that it does something useful for us. Then we will be motivated to attain the goal of achieving our solution when things get tricky. Therefore our program will take text that we enter and apply a ‘Substitution cipher’ and tell us the result. That result we can then enter again and get what we originally typed back.

Disclaimers

Ubuntu® is a registered trademark of Canonical Ltd – ubuntu.com/legal/intellectual-property-policy.

Other names / logos can be trademarks of their respective owners. Please review their website for details.

I am independent from the organisations mentioned and am in no way writing for or endorsed by them.

The information presented in this article is written according to my own understanding, there could be technical inaccuracies, so please do undertake your own research.

Featured image of the duck is my copyright, please don’t use without my permission.

References

The cipher

The cipher that I’ve come up with takes a letter and substitutes it for another and then maps that letter back to the original. This is so that we don’t have separate encode and decode operations. The cipher is performing a ‘shift’ and then ‘flip’ operation, for example, with a ‘shift’ of the letters four places to the right where the end loops around to the start, then we flip, then we have:

Diagram showing abcdefghijklmnopqrstuvwxyz@*.: shifted to the right by four characters to become defghijklmnopqrstuvwxyz@*.:abc which then is used for the flip map, so d becomes c, e becomes b, f becomes a, g becomes : etc.

The implementation

This time I have commented the code throughout to explain what each part does:

/*
 * Flipper
 *
 * Flips an entered string with a simple substitution cypher.
 *
 * @copyright  2022 G J Barnard
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
 */

// Import classes we need.
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.util.HashMap;

/**
 * Flipper class.
 * @author G J Barnard
 */
public class Flipper {
    private InputStreamReader isr = null; // Stream to get the key presses from the keyboard.
    private BufferedReader br = null; // Buffer to store the pressed keys from the input stream.
    private HashMap<Character, Character> map = null; // Map to store the character to character relationship.

    private static final boolean DEBUG = false; // Show the debug messages.  Static so recompile when change.
    private static final int DIFFERENCE = 4; // How many characters to 'shift to the left' the characters we flip.

    // Characters we flip, both strings must be the same length so that the combined tally of the two is even.
    private static final String LOWER = "abcdefghijklmnopqrstuvwxyz@*.:";
    private static final String UPPER = "ABCDEFGHIJKLMNOPQRSTUVWXYZ&%#;";

    /**
     * Main program entry point.
     * @param args Not used.
     */
    public static void main(String args[]) {
        Flipper us = new Flipper(); // Instantiate our class.

        System.out.println("Flipped: " + us.flip("Flip   : ")); // Flip with the before and after string prefixes.
    }

    /**
     * Constructor to setup the input and create the character code map.
     */
    public Flipper() {
        this.isr = new InputStreamReader(System.in); // Attach a stream to the system input.
        this.br = new BufferedReader(this.isr); // Attach a buffer to store the pressed keys.

        // Convert the string to characters we can access via an array index with a number.
        char[] lowerChars = LOWER.toCharArray();
        char[] upperChars = UPPER.toCharArray();
        // Array indexes start at zero and not one, but the length is the actual number of characters.
        int lowerMax = lowerChars.length - 1;
        // Ditto for indexing with the difference.
        int difference = Flipper.DIFFERENCE - 1;
        int fromIndex; // From character.
        int toIndex; // To character.

        // Confirm our logic for looping around the toIndex when its larger than the number of characters.
        if (Flipper.DEBUG) {
            for (int index = 0; index <= lowerMax; index++) {
                fromIndex = index + difference;
                if (fromIndex > lowerMax) {
                    fromIndex = fromIndex - lowerMax;
                }
                System.out.println(index + " -> " + fromIndex);
            }
        }
        
        int half = (lowerMax + 1) / 2;  // Find the mid point in the character string.
        int mapLength = lowerChars.length * 2; // Work out how big the map will be.
        this.map = new HashMap<>(mapLength);  // Construct the map, character to character.
        for (int index = 0; index <= half; index++) {
            fromIndex = index + difference; // Shift.
            if (fromIndex > lowerMax) {
                fromIndex = fromIndex - lowerMax;
            }

            toIndex = (lowerMax - index) + difference; // Flip.
            if (toIndex > lowerMax) {
                toIndex = (toIndex - 1) - lowerMax;
            }

            // Check the mappings.
            if (Flipper.DEBUG) {
                System.out.println(
                    index + " (From: " + fromIndex + " To: " + toIndex + ")" +
                    ": " + lowerChars[fromIndex] + " -> " + lowerChars[toIndex] + " -> " + lowerChars[fromIndex] +
                    " & " + upperChars[fromIndex] + " -> " + upperChars[toIndex] + " -> " + upperChars[fromIndex]
                );
            }
            
            // Add the key value pairs to the map.  For example, a -> z and back again, z -> a.
            this.map.put(lowerChars[fromIndex], lowerChars[toIndex]);
            this.map.put(lowerChars[toIndex], lowerChars[fromIndex]);
            this.map.put(upperChars[fromIndex], upperChars[toIndex]);
            this.map.put(upperChars[toIndex], upperChars[fromIndex]);
        }
        
        // Check that the map is what we intended.
        if (Flipper.DEBUG) {
            for (int index = 0; index < lowerChars.length; index++) {
                System.out.println(
                    index + ": " + lowerChars[index] + " -> " + this.map.get(lowerChars[index]) +
                    " & " + upperChars[index] + " -> " + this.map.get(upperChars[index])
                );
            }
        }
    }

    /**
     * Flip the entered text.
     * @param message What to show as the input question.
     * @return 
     */
    private String flip(String message) {
        System.out.print(message);

        // Using a 'try / catch' block as reading key presses is an input output operation that can fail.
        String input;
        try {
            input = br.readLine(); // Read a line of text from the user.
        } catch (java.io.IOException ioe) { // An error has happened when getting the text from the user.
            // Tell the user what the error is.
            System.out.println(); // New line after the message.
            this.processError(ioe);
            return ("");
        }

        /* Using a 'try / catch' block as pressing 'Ctrl-C' during input caused the 'input' to be null and
           so the 'toCharArray()' call fails. */
        char[] inputChars;
        try {
            // Convert the text from the user (string) into an array of indexable characters.
            inputChars = input.toCharArray();
        } catch (java.lang.NullPointerException npe) {
            // Tell the user what the error is.
            System.out.println();
            this.processError(npe);
            return ("");       
        }

        Character theChar; // Temporary store for the character in the 'type' we need it in from the map.

        // Process each character in the input text from the user.
        for (int index = 0; index < inputChars.length; index++) {
            theChar = this.map.get(inputChars[index]); // Get the mapped character, key -> value.
            if (theChar != null) { // If the character is mapped then flip, otherwise it will not be transposed.
                /* Replace the entered character with the mapped one.  Being clear here with the Character to char
                   type conversion.  Removing 'charValue()' call will still work. */
                inputChars[index] = theChar.charValue();
            }
        }

        return new String(inputChars); // Return the flipped entered text as a string we can output.
    }

    /**
     * Tell the user what happened.
     * @param ex The exception to process.
     */
    private void processError(java.lang.Exception ex) {
        System.err.print("Ops! -> "); // For some reason string concatenation does not work here.
        System.err.print(ex.getLocalizedMessage()); // What happened.
        System.err.print(" @ ");                    // And.
        System.err.println(ex.getStackTrace()[0]);  // Where.
    }
}

What exactly is going on? Well:

  1. Firstly we create the cipher with the characters we wish to substitute in the ‘Flipper’ classes ‘Constructor’. If the character is not known, then it is not changed but passed straight through to the output. The ‘mapping’ between the characters is stored in a ‘HashMap’ object. This is done by processing the characters we substitute one by one, and so our ‘String’ constants ‘LOWER’ and ‘UPPER’ need to be accessed as individual ‘Character’s in an ‘array’. The array instead of having the first index as ‘1’ is actually ‘0’, this I think in Java is from origins in the ‘C’ language whereby an array starts at a given memory location, and so the index is an ‘offset’ from it times the size of each element in the array. Thus if our characters are 8 bit ones, a byte, then with a base address of ‘0x0000ff00’, then the fourth character in our string will be at index ‘3’, being address ‘0x0000ff03’, if the characters are 16 bits long, two bytes, the index ‘3’ will be address ‘0x0000ff06’. To see what’s going on with the mapping, change the constant ‘DEBUG’ to ‘true’ and recompile.
  2. There is an ‘InputStreamReader’ that we connect to the ‘input’ of the computer, being in this case the keyboard, that then is connected to a ‘BufferedReader’ object that will store the string we enter.
  3. Then we have a method ‘flip’ that takes as a parameter the prefix to the result of the flipped string. It gets the string from the user, then substitutes the characters in the entered string by processing each character one by one and then ‘asking’ the ‘map’, ‘what character does this character map to?’. This is done by the fact that we have ‘mapped’ a character, an ‘index’, to another character, its ‘value’. If the character is not in the ‘map’ then its copied to the output. If any issues happen with processing the input from the keyboard or converting it into an array of characters, then this is ‘caught’ as an ‘exception’ and an error message shown to the user. Finally, it returns the result as a new string back to the ‘main()’ method that then outputs it to the console with the prefix string of “Flipped: “.

And so with the text of ‘eLearningWorld’ we get:

Running 'Flipper' with 'java Flipper' to transform 'eLearningWorld' to 'bUbfsw*w:Nvsyc' and back again.

Which given the effort does not seem that much! But then software does tend to be a bit of an iceberg, what you see on the surface is little in comparison to what is underneath.

If you’d like to try for yourself and don’t want to copy / paste from here, then you can get a copy from raw.githubusercontent.com/gjb2048/code/f817624/Java/Flipper.java.

Conclusion

Do have a go and do comment what you think in the comments.

Gareth Barnard
Latest posts by Gareth Barnard (see all)
blank

Gareth Barnard

Gareth is a developer of numerous Moodle Themes including Essential (the most popular Moodle Theme ever), Foundation, and other plugins such as course formats, including Collapsed Topics.

One thought on “A little bit more Java

Add a reply or comment...