Toolkit: A Simple Calculator

     

This program demonstrates how you might write a simple calculator in Java. It incorporates a number of the programming constructs we've covered so far in this book, and it could be usefully incorporated into other applications. The main thing it does that is of interest here is that it uses the standard input and shows how to read user -entered data in a complete, working app.

It demonstrates using File I/O (BufferedReader and InputStreamReader), using regular expressions, and exception handling.

 

 package net.javagarage.apps.calculator; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.util.regex.Matcher; import java.util.regex.Pattern; /**<p>  * Demos a working calculator, usable from the  * console. The advantage is that it shows, in  * a small program, how to fit different pieces  * together.  * <p>  * More specifically, it shows how to read in  * data the user types on the console, and how  * to parse strings in a real-world way.  * <p>  * Limitations include the fact that a legal  * expression is constituted by two operands  * and one operator; i.e., we can't do this:  * 4 * (5 + 6)  * </p>  * @author eben hewitt  * @see BufferedReader, Float, String, Pattern  **/ public class Calculator { private String readInput() {     String line = "";     try {     //get the input     BufferedReader in = new BufferedReader(new InputStreamReader(System.in));     while((line = in.readLine()) != null) {         //strip spaces out and do calculation         line = "result: " +                 calculate(line.replaceAll(" ", ""));     }     } catch (IOException e) {         System.err.println(e.getMessage());     } catch (NumberFormatException nfe){         System.out.println("Error->" +                 nfe.getMessage());         System.out.println("Enter an expression: ");         //recursive call. Otherwise the program stops.         readInput();     }     return line; } private float calculate(String line){     //these will hold the two operands     float first, second;     String op = "";     int opAt = 0;     //build regex to parse the input     //because * is an operator in regex, we     //need to escape it.     Pattern p = Pattern.compile("[+/-[*]]");     Matcher m = p.matcher(line);     //when you find an arithmetic operator in the     //line, get its position     if (m.find()){         opAt = m.start();     } else {         //user didn't enter a valid expression         System.out.println("usage-> Enter a valid                    expression");         return Float.NaN;     }     //get the part of the string before the operator     //that is our first operand     first = Float.parseFloat(line.substring(0, opAt));     //find the occurrence of one of the operator     //symbols     op = line.substring(opAt, opAt+1);     //get the number between the operator and the end     //of the string; that's our other operand     second = Float.parseFloat(line.substring(opAt+1,     line.length()));     if (op.equals("*")){         System.out.println(first * second);     }     if (op.equals("+")){         System.out.println(first + second);     }     if (op.equals("-")){         System.out.println(first - second);     }     if (op.equals("/")){         System.out.println(first / second);     }     return Float.NaN; } /**  * You can hit CTRL + C to cancel the program  * and start over.  * @param args  */ public static void main(String[] args) {     System.out.println("Enter an expression: ");     new Calculator().readInput(); } } 

Result

Here is what a sample execution of the program looks like:

 

 Enter an expression: 655+99807 100462.0 65 * 876 56940.0 510 / 70 7.285714 34 - 9876 -9842.0 fake input usage-> Enter a valid expression 

The limitations of this program are that it doesn't handle nested expressions and it doesn't have a control for the user to stop the program; it will just run until you manually shut it down.

Thread Issues with Runtime. exec ()

Stuff doesn't always come out as we plan. Sometimes things go wrong in our code. But sometimes, we just haven't done as much as we could have to plan for contingencies.

The previous examples of using the System.in stream work fine. If you need to access System.err during execution of your program, though, you have two streams coming in, and only one place to read them. You have tried to be a good programmer, understanding that sometimes bad things happen to good programs, and handle them. But this presents a special kind of difficulty. Here is the common problem.

Your program tries to read from the standard input, and then tries to read from the standard error stream. This doesn't seem like what you want, but how else are you going to do it, as your while loop processes? You can't read from both standard in and standard error at the same time, right? (You can, and should). Because your process could instantly try to write to standard error (if something goes wrong in your program before anything is written to standard in). If that happens, your program could block the on in.readLine() , waiting for the process to write its data. However, back at the ranch, the program is trying to write to standard error. It might be expecting you to read standard err and free up the buffer. But you don't do this because you are waiting for the process to write to standard error. You can see where this is headed. Deadlock.

This is when threads are really called for. Look at your program and honestly answer this question: "How many things am I trying to do here?" If the answer is more than one, ask yourself this: "Do any of these tasks require waiting for event?" If the answer is yes, that guy is a good candidate for a thread. Doing things in one thread can be more complex obviously, but often prevents your application from crashing after many pending and unresolved issues are piled up higher and higher without chance at resolution. The best-case scenario here is usually very poor application performance.

To achieve this smoother functionality, we need to do a little work of our own. Specifically, we need two classes: one to represent threads for standard input and standard error, and another to be the app that places the call to runtime.exec() . First, the mama app.

MultithreadedSystem
 

 package net.javagarage.sys; /**  * <p>  * Demonstrates use of threads to handle standard  * error stream interruptions of input in order  * to avoid deadlock.  *  * @see Thread, ThreadedStreamReader  * @author eben hewitt  */ public class MultithreadedSystem { public static void main(String[] args) { MultithreadedSystem m = new MultithreadedSystem(); try { //m.doWork(new String[]{"explorer", "dude"}); //try using a program that uses the stdin, like this, //remember that each command must be separate m.doWork(new String[] {"cmd", "/c", "start", "java", "net.javagarage.apps.calculator.Calculator"}); } catch (Exception e){ e.printStackTrace(); } System.out.println("All done"); } /**  * This is where you would do whatever work with the  * standard input that you want.  * @param String[] command The name of the program  * that you want to execute, including arguments to  * the program.  * @throws Exception  */ public void doWork(String[] command) throws Exception { //this will hold the number returned by the spawned app int status; //use buffers to stand in for error and output streams StringBuffer err = new StringBuffer(); StringBuffer out = new StringBuffer(); //start the external program Process process = Runtime.getRuntime().exec(command); //create thread that reads the input stream for this process ThreadedStreamReader stdOutThread = new ThreadedStreamReader(process.getInputStream(), out); //create thread that reads this process' //standard error stream ThreadedStreamReader stdErrThread = new ThreadedStreamReader(process.getErrorStream(), err); //start the threads stdOutThread.start(); stdErrThread.start(); //this method causes the current thread to wait until //the process object (the running external app) //is terminated. status = process.waitFor(); //read anything still in buffers. //join() waits for the threads to die. stdOutThread.join(); stdErrThread.join(); //everything is okay if (status == 0) { System.out.println(command[0] + " ran without errors."); //you can print the values of the out //and err here if you want //if the result is not 0, the external app //threw an error //note that this is by convention only, though most //programs will follow it } else { System.out.println(command[0] + " returned an error status: " + status); //you can print the values of the out and //err here if you want } } } 

The second class will extend thread, and we'll direct the output using it.

ThreadedStreamReader
 

 package net.javagarage.sys; import java.io.InputStreamReader; import java.io.InputStream; /**  * <p>  * File: ThreadedStreamReader  * Purpose: To ensure access to standard error  * System.err when reading a buffered stream with  * System.in.  * <p>  * Note that typically in your programs you want to  * implement the Runnable interface to do  * multithreading, but here we are only ever going to  * use this class for this thread-specific purpose,  * so it is okay. It is preferable to implement  * Runnable in general, because then you are free to  * extend a different class (and Java does not allow  * multiple inheritance).  *  * @author eben hewitt  */ public class ThreadedStreamReader extends Thread { private StringBuffer outBuffer; private InputStreamReader inputStream; //constructor accepts an input *(//for instance, and /**  * @param InputStream either standard err or standard in  * @param StringBuffer  */ public ThreadedStreamReader(InputStream in, StringBuffer out){ outBuffer = out; inputStream = new InputStreamReader(in); } //override run() to do the thread's work public void run() { int data; try { //hold character data in the buffer until we get to the end while((data = inputStream.read()) != -1) outBuffer.append((char)data); } catch (Exception e) { //tack the error message onto the end of the data stream outBuffer.append("\nError reading data:" + e.getMessage()); } } } 

Executing this application with arguments of explorer and dude gets us the error shown in Figure 31.1.

Figure 31.1. Windows Explorer starts up but cannot find this directory and returns an error status, which we capture.

graphics/31fig01.gif


Our application then prints the following and exits:

 

 Process explorer returned an error status: 1 All done 

Of course, executing that program doesn't give us access to these streams, so it isn't entirely useful, but it proves in the simplest possible manner that our program does work. Which is not a bad practice.

Now, if you have the Calculator program compiled, you should be able to call it just as I do here. If it won't run, perhaps because you get an IOException: Cannot create process, error=2 or something like that, make sure that your path is set up correctly. You can check if your system can execute the Java command by opening a command prompt and typing java . If usage information prints out, you're in business. So go ahead and run it.

If everything goes as planned, our call to the Java calculator program should look like this after we're finished using it.

 

 cmd ran without errors. All done 

I love it when a plan comes together.

Now let's look at another useful example of getting the runtime to execute something.

Toolkit: Getting the MAC Address from Your NIC

To show that there are useful things that are made possible with this business,, and to do something that's possibly useful, let's write a program that works on both Linux and Windows and calls programs that ship with each of those operating systems. Let's get the MAC address off of our network cards.

MACAddress.java
 

 package net.javagarage.misc; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.util.ArrayList; /**<p>  * Uses system tools in windows or linux to  * get the user's Media Access Control addresses  * and print them out.  * <p>  * Demonstrates practical use of runtime.exec().  * On Linux, you must be root to run it.  * <p>  * One shortcoming of this program is that if you  * encounter any errors in the process, they aren't  * registered.  **/ public class MACAddress { //if true, the messages passed into the log() method //will print; if false, they are ignored static boolean debug = true; /**  * Determine what OS we are on, and call a different  * program to do our work depending on the result.  * @return  */ public static ArrayList getMACAddresses(){ //what regex matches the String representation //of the address returned? String matcher = ""; //what program is used on this OS to get the MAC address? String program = ""; //how many places does this address String require //us to move back? int shiftPos = 14; //different operating systems have different programs //that get info about the network hardware String OS = System.getProperty("os.name"); if (OS.startsWith("Windows")){ /* ipconfig returns something like this:  * 00-08-74-F5-20-CC  */ matcher = ".*-..-..-..-..-.*"; program = "ipconfig.exe /all"; } if (OS.startsWith("Linux")){ /*ifconfig returns something like this: "HWaddr 00:50:FC:8F:36:1A" the lastIndexOf method will set the substring to start 14 spaces back from the last :, which is the beginning of the address */ shiftPos = -39; matcher = ".*:..:..:..:..:.*"; program = "/sbin/ifconfig"; } //sorry no macintosh return getMACAddresses(matcher, program, shiftPos); } /**  * Overloaded method uses the program that  * ships with the current OS and parses the string  * that it returns.  * @return String The address of each  * device found on the system.  */ private static ArrayList getMACAddresses(String matcher, String program, int shiftPos){ String line = ""; Process proc; ArrayList macAddresses = new ArrayList(2); try { //run the windows program that prints the MAC address proc = Runtime.getRuntime().exec(program); BufferedReader in = new BufferedReader(new InputStreamReader(proc.getInputStream())); while((line = in.readLine()) != null) { //the matches method determines if a given //String matches the passed regular expression if (line.matches(matcher)) { int pos = line.lastIndexOf("-")-shiftPos; //add the address to the list macAddresses.add(line.substring(pos, line.length())); } } } catch (IOException e) { log("Error using ipconfig: " + e.getMessage()); proc = null; System.exit(-1); } return macAddresses; } /**  * Print out the info we parsed. These are separated  * methods because you might want to do something  * other than just print it out.  * @param devices  */ public static void printAddresses(ArrayList devices){ int numberDevices = devices.size(); System.out.println("Devices found: " + numberDevices); for(int i=0; i < numberDevices; i++) { System.out.println("Device " + i + ": " + devices.get(i)); } } //convenience method for printing messages to stdout private static void log(String msg){ if (debug){ System.out.println(">" + msg); } } //start program public static void main(String[] args) { printAddresses(getMACAddresses()); System.exit(0); } } //eof 

The result is shown here:

 

 Devices found: 2 Device 0: 00-20-A6-4C-93-40 Device 1: 00-04-76-4D-D6-B5 

My laptop has one standard Ethernet adapter (device 1) and one wireless 802.11 card (device 0). Note that you can toggle the debug boolean to print out information regarding the current state of the program.



Java Garage
Java Garage
ISBN: 0321246233
EAN: 2147483647
Year: 2006
Pages: 228
Authors: Eben Hewitt

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net