Some more Java

Thursday, 16 February 2023

First posted on eLearningWorld.

As a follow on from ‘A little bit more Java’, this time we will progress to look at how we can input from the command line and enhance our program even further. The beginnings of a program can be an uphill struggle as we work away to get something that actually does something. Now we’ve made progress, that hill will start to soften and we’ll be able to add more functionality now that we have our base.

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.

References

The parameters

Command line parameters are another way of getting input into a program. They can be especially useful when they provide configuration options specific to your needs, and / or you need to schedule the run for another time with specific data. The parameters can be anything that is suitable to be inputted in this way, i.e. relatively small elements of data. With our program we not only have configuration parameters, but also the text to flip too.

If we run the program with the help parameter, -?, then the program will tell us all of the options:

Flipper help

The implementation

As before, I have commented the code throughout to explain what each part does:

  1/*
  2 * Flipper
  3 *
  4 * Flips an entered string with a simple substitution cypher.
  5 *
  6 * @copyright  2022 G J Barnard
  7 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  8 */
  9
 10// Import classes we need.
 11import java.io.BufferedReader;
 12import java.io.InputStreamReader;
 13import java.util.ArrayList;
 14import java.util.HashMap;
 15import java.util.Iterator;
 16
 17/**
 18 * Flipper class.
 19 * @author G J Barnard
 20 */
 21public class Flipper {
 22    private InputStreamReader isr = null; // Stream to get the key presses from the keyboard.
 23    private BufferedReader br = null; // Buffer to store the pressed keys from the input stream.
 24    private HashMap<Character, Character> map = null; // Map to store the character to character relationship.
 25    private ArrayList<String> ourArgs = null; // Arguments.
 26
 27    private boolean DEBUG = false; // Show the debug messages.  Static so recompile when change.
 28    private int DIFFERENCE = 4; // How many characters to 'shift to the left' the characters we flip.
 29
 30    // Characters we flip, both strings must be the same length so that the combined tally of the two is even.
 31    private static final String LOWER = "abcdefghijklmnopqrstuvwxyz@*.:";
 32    private static final String UPPER = "ABCDEFGHIJKLMNOPQRSTUVWXYZ&%#;";
 33
 34    /**
 35     * Main program entry point.
 36     * @param args Not used.
 37     */
 38    public static void main(String args[]) {
 39        Flipper us = new Flipper(args); // Instantiate our class.
 40
 41        System.out.println("Flipped: " + us.flip("Flip   : ")); // Flip with the before and after string prefixes.
 42    }
 43
 44    /**
 45     * Constructor to setup the input and create the character code map.
 46     * @param args The arguments.
 47     */
 48    public Flipper(String args[]) {
 49        // Check arguments.
 50        this.checkArgs(args);
 51
 52        // Convert the string to characters we can access via an array index with a number.
 53        char[] lowerChars = LOWER.toCharArray();
 54        char[] upperChars = UPPER.toCharArray();
 55        // Array indexes start at zero and not one, but the length is the actual number of characters.
 56        int lowerMax = lowerChars.length - 1;
 57        // Ditto for indexing with the difference.
 58        int difference = this.DIFFERENCE - 1;
 59        int fromIndex; // From character.
 60        int toIndex; // To character.
 61
 62        // Confirm our logic for looping around the toIndex when its larger than the number of characters.
 63        if (this.DEBUG) {
 64            System.out.println("Difference is: " + this.DIFFERENCE);
 65            System.out.println();
 66            System.out.println("Loop check:");
 67            for (int index = 0; index <= lowerMax; index++) {
 68                fromIndex = index + difference;
 69                if (fromIndex > lowerMax) {
 70                    fromIndex = fromIndex - lowerMax;
 71                }
 72                System.out.println(index + " -> " + fromIndex);
 73            }
 74            System.out.println();
 75        }
 76
 77        int half = (lowerMax + 1) / 2;  // Find the mid point in the character string.
 78        int mapLength = lowerChars.length * 2; // Work out how big the map will be.
 79        this.map = new HashMap<>(mapLength);  // Construct the map, character to character.
 80        if (this.DEBUG) {
 81            System.out.println("Mappings:");
 82        }
 83        for (int index = 0; index <= half; index++) {
 84            fromIndex = index + difference; // Shift.
 85            if (fromIndex > lowerMax) {
 86                fromIndex = fromIndex - lowerMax;
 87            }
 88
 89            toIndex = (lowerMax - index) + difference; // Flip.
 90            if (toIndex > lowerMax) {
 91                toIndex = (toIndex - 1) - lowerMax;
 92            }
 93
 94            // Check the mappings.
 95            if (this.DEBUG) {
 96                System.out.println(
 97                    index + " (From: " + fromIndex + " To: " + toIndex + ")" +
 98                    ": " + lowerChars[fromIndex] + " -> " + lowerChars[toIndex] + " -> " + lowerChars[fromIndex] +
 99                    " & " + upperChars[fromIndex] + " -> " + upperChars[toIndex] + " -> " + upperChars[fromIndex]
100                );
101            }
102
103            // Add the key value pairs to the map.  For example, a -> z and back again, z -> a.
104            this.map.put(lowerChars[fromIndex], lowerChars[toIndex]);
105            this.map.put(lowerChars[toIndex], lowerChars[fromIndex]);
106            this.map.put(upperChars[fromIndex], upperChars[toIndex]);
107            this.map.put(upperChars[toIndex], upperChars[fromIndex]);
108        }
109
110        // Check that the map is what we intended.
111        if (this.DEBUG) {
112            System.out.println();
113            System.out.println("The map is:");
114            for (int index = 0; index < lowerChars.length; index++) {
115                System.out.println(
116                    index + ": " + lowerChars[index] + " -> " + this.map.get(lowerChars[index]) +
117                    " & " + upperChars[index] + " -> " + this.map.get(upperChars[index])
118                );
119            }
120            System.out.println();
121        }
122    }
123
124    /**
125     * Check the arguments
126     *
127     * Note: The exit status code, derived from 'C's 'errno.h', see: https://en.wikipedia.org/wiki/Errno.h
128     *
129     * @param args The arguments.
130     */
131    private void checkArgs(String args[]) {
132        if (args.length > 0) {
133            for (String var: args) {
134                if (var.charAt(0) == '-') { // Argument prefix.
135                    String param = var.substring(1);
136                    if (param.charAt(0) == '?') { // Help.
137                        this.displayHelp();
138                        System.exit(0); // Bye!
139                    }
140                    if (param.charAt(0) == 'd') { // Debug.
141                        // Debug.
142                        this.DEBUG = true;
143                        continue;
144                    }
145                    try { // If a number then this is the difference to use instead of the default assigned during construction.
146                        this.DIFFERENCE = Integer.parseInt(param);
147                    } catch (NumberFormatException nfe) { // Ops!
148                        System.out.println("Invalid argument: " + param);
149                        System.out.println();
150                        this.displayHelp();
151                        System.exit(22); // EINVAL - Invalid argument.
152                    }
153                } else { // We have text to flip.  The command line is delimited by a space, so we can have many words.
154                    if (this.ourArgs == null) {
155                        this.ourArgs = new ArrayList<>();
156                    }
157                    this.ourArgs.add(var);
158                }
159            }
160        }
161    }
162
163    /**
164     * Display the help.
165     */
166    private void displayHelp() {
167        System.out.println("Usage: ");
168        System.out.println("java Flipper [arguments] [text to flip or leave empty to be asked]");
169        System.out.println("Optional arguments:");
170        System.out.println("-[number] The difference.");
171        System.out.println("-d Turn on debugging.");
172        System.out.println("-? Show This help.");
173    }
174
175    /**
176     * Flip the entered text.
177     * @param message What to show as the input question.
178     * @return
179     */
180    private String flip(String message) {
181        String input;
182
183        if (this.ourArgs == null) { // No text to flip, so ask.
184            System.out.print(message);
185
186            this.isr = new InputStreamReader(System.in); // Attach a stream to the system input.
187            this.br = new BufferedReader(this.isr); // Attach a buffer to store the pressed keys.
188
189            // Using a 'try / catch' block as reading key presses is an input output operation that can fail.
190            try {
191                input = br.readLine(); // Read a line of text from the user.
192            } catch (java.io.IOException ioe) { // An error has happened when getting the text from the user.
193                // Tell the user what the error is.
194                System.out.println(); // New line after the message.
195                this.processError(ioe);
196                return ("");
197            }
198        } else { // Get the text supplied on the command line and concatenate into one line.
199            input = new String();
200            Iterator<String> args = this.ourArgs.iterator(); // Iterate over each word.
201            while (args.hasNext()) { // Another word?
202                input += args.next(); // Get the word.
203                if (args.hasNext()) {
204                    input += " "; // Put the spaces back.
205                }
206            }
207        }
208
209        /* Using a 'try / catch' block as pressing 'Ctrl-C' during input caused the 'input' to be null and
210           so the 'toCharArray()' call fails. */
211        char[] inputChars;
212        try {
213            // Convert the text from the user (string) into an array of indexable characters.
214            inputChars = input.toCharArray();
215        } catch (java.lang.NullPointerException npe) {
216            // Tell the user what the error is.
217            System.out.println();
218            this.processError(npe);
219            return ("");
220        }
221
222        Character theChar; // Temporary store for the character in the 'type' we need it in from the map.
223
224        // Process each character in the input text from the user.
225        for (int index = 0; index < inputChars.length; index++) {
226            theChar = this.map.get(inputChars[index]); // Get the mapped character, key -> value.
227            if (theChar != null) { // If the character is mapped then flip, otherwise it will not be transposed.
228                /* Replace the entered character with the mapped one.  Being clear here with the Character to char
229                   type conversion.  Removing 'charValue()' call will still work. */
230                inputChars[index] = theChar.charValue();
231            }
232        }
233
234        return new String(inputChars); // Return the flipped entered text as a string we can output.
235    }
236
237    /**
238     * Tell the user what happened.
239     * @param ex The exception to process.
240     */
241    private void processError(java.lang.Exception ex) {
242        System.err.print("Ops! -> "); // For some reason string concatenation does not work here.
243        System.err.print(ex.getLocalizedMessage()); // What happened.
244        System.err.print(" @ ");                    // And.
245        System.err.println(ex.getStackTrace()[0]);  // Where.
246    }
247}

What’s changed?

In essence:

  • A new method called ‘checkArgs’ to check and interpret any arguments.
  • A new method called ‘displayHelp’, to well, display the help.
  • The method ‘flip’ has been changed to detect if there is text from the arguments to flip, and if so uses it instead of asking.
  • The DEBUG and DIFFERENCE attributes can now be set dynamically.
  • I’ve added introductions to the debug information.

If you’d like to compare the changes then please look at: github.com/gjb2048/code/compare/0ff7399~3...0ff7399 .

Running

And so with the arguments of ‘-24 This is eLearningWorld’ we get:

java Flipper -24 This is eLearningWorld

Then if we flip back, then we get:

java Flipper -24 &ji* i* mFma.didkXc.fn

But! We have an issue with the text, so we need to encapsulate the text in some quotes:

java Flipper -24 “&ji* i* mFma.didkXc.fn

Now it works!

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/0ff7399/Java/Flipper.java .

Conclusion

Do have a go.