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("
Error 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.


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.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.18.220.243