/************************************************************************ HostConvert Written by Paul Serice. 1997, under Linux 2.0.30 using JDK 1.1.1. It is released into the public domain and is freely modifiable except that the following paragraph must always be redistributed with it: HostConvert was originally written by Paul Serice who released it into the public domain for others to use responsibly and at their own risk. The HostConvert program does the usual converting of an internet name to an internet address. This particular program is purposefully designed to fit all within one class. I didn't think something so small (about 10k) should require more tha one file to distribute. THREADS: If you try to read the source code, you should be informed of how I have set up the communication among the threads. It's a home-spun method, but works well. Basically, it just uses boolean variables in memory shared by all the threads so the different threads will know what state the program is in. To get the threads to block, I use wait() and sleep(). To unblock, I use notifyAll() and interrupt(). When the program is first started, it spawns two threads which persist until the program is exited. The first thread ("t1") is used to count the number of seconds spent waiting for the program to resolve an address. The second thread ("t2") is used to launch a third thread ("t3") which does the actual name resolution. This third thread does it's job and then expires unless the user presses the "Abort" button. If the user aborts, t2 stops t3. The two threads which live for the duration of the program block when they have nothing better to do. When an ActionEvent (button pressed or enter key pressed) comes in, the ActionProcess takes note of the user's input and places it in shared memory. Also, if the user tries to abort, the ActionProcess checks to see if the abort window is open. If it is, it interrupts t2 which is waiting for t3 to finish. t2 immediately proceeds to stop t3 thus effecting the abort. CONTROLLING THE THREADS: (1) When you start the program, both t1 and t2 block on the "this" object. (If I don't keep programming, I know I'm going to forget. So here it goes.) Take a look at the object class. The most basic methods are those which control threads. You block because you are waiting (as in "wait()") for an object to change. When that object is changed, you want to be notified (as in "notified()"). Because other threads also might be waiting for a change in the same object, the notification is sent to the object which then releases one thread (notify()) or all threads gradually (notifyAll()). (2) When the ActionProcess receives a mouse click etc. which should start the name lookup process, it sends a notifyAll() to the "this" object. These means both t2 and t1 will start running. (3) t2 spaws t3 and waits for it to finish or for a user abort as described above. (4) After t3 finishes or the user aborts, t2 lets t1 know it should stop counting seconds by interrupting t1. (5) Because t2 needs to be the last thread to write to the screen, it begins to wait until t1 tells it the coast is clear for it to do a write. t1 then begins to block waiting for the cycle to start over. (6) t2 then does its write. It too then begins to block waiting for the cycle to start over. ************************************************************************/ import java.awt.*; import java.awt.event.*; import java.net.*; import java.lang.Thread; public class HostConvert implements Runnable, ActionListener, WindowListener { private Frame mainframe = null; // Main window frame. Button bexit = null; // Exit button. Button bname = null; // Name button. Button baddress = null; // Address button. TextField tfname = null; // Text field for name of machine. TextField tfaddress = null; // Text field for IP address. Label lerror = null; // Label for errors. // MEMORY SHARED BY THE THREADS static HostConvert h = null; InetAddress address = null; // Internet Address to lookup. private String query = null; // Which machine name or address to query private String source = null; // Which field was the source. private boolean t1waitingOnUser = false; // These two let the program know private boolean t2waitingOnUser = false; // that we're ready for a new // lookup. private boolean toolatetoabort = true; // You start out not being able to // abort. static Thread t1 = null; static Thread t2 = null; static Thread t3 = null; public synchronized boolean gett1waitingOnUser() { return t1waitingOnUser; } public synchronized boolean gett2waitingOnUser() { return t2waitingOnUser; } public synchronized boolean gettoolatetoabort() { return toolatetoabort; } public synchronized String getquery() { return query; } public synchronized void setquery(String query) { this.query = query; } public synchronized String getsource() { return source; } public synchronized void setsource(String source) { this.source = source; } // This is just a helper function to build the GridBagLayout void buildconstraints(GridBagConstraints gbc, int gx, int gy, int gw, int gh, int wx, int wy) { gbc.gridx = gx; gbc.gridy = gy; gbc.gridwidth = gw; gbc.gridheight = gh; gbc.weightx = wx; gbc.weighty = wy; } public void makemainframe() // This is where the main window is created { // and shown. mainframe = new Frame(); //Create a new window. GridBagConstraints gbc = new GridBagConstraints(); GridBagLayout gridbag = new GridBagLayout(); //Set up Closing the window. Without this, you'll have to "Kill" //the window manually. mainframe.addWindowListener(this); mainframe.setLayout(gridbag); mainframe.setSize(420,130); mainframe.setTitle("HostConvert"); mainframe.setBackground(Color.lightGray); //Add the "Name" button. bname = new Button("Name"); bname.addActionListener(this); buildconstraints(gbc,0,0,1,1,0,100); gbc.anchor = GridBagConstraints.EAST; gbc.fill = GridBagConstraints.HORIZONTAL; gbc.ipadx = 5; gridbag.setConstraints(bname,gbc); mainframe.add(bname); //Add the "Name" Text Field. tfname = new TextField(40); tfname.setName("Name"); tfname.addActionListener(this); buildconstraints(gbc,1,0,2,1,100,100); gbc.anchor = GridBagConstraints.WEST; gbc.fill = GridBagConstraints.HORIZONTAL; gridbag.setConstraints(tfname,gbc); mainframe.add(tfname); //Add the "Address" button. baddress = new Button("Address"); baddress.addActionListener(this); buildconstraints(gbc,0,1,1,1,0,100); gbc.anchor = GridBagConstraints.EAST; gbc.fill = GridBagConstraints.HORIZONTAL; gridbag.setConstraints(baddress,gbc); mainframe.add(baddress); //Add the "Address" Text Field. tfaddress = new TextField(40); tfaddress.setName("Address"); tfaddress.addActionListener(this); buildconstraints(gbc,1,1,2,1,0,100); gbc.anchor = GridBagConstraints.WEST; gbc.fill = GridBagConstraints.HORIZONTAL; gridbag.setConstraints(tfaddress,gbc); mainframe.add(tfaddress); //Add the "Error" Label. lerror = new Label("Enter Address "); lerror.setAlignment(lerror.LEFT); lerror.setFont(new Font("Monospaced",Font.PLAIN,12)); buildconstraints(gbc,0,2,2,1,0,100); gbc.anchor = GridBagConstraints.WEST; gbc.fill = GridBagConstraints.HORIZONTAL; gridbag.setConstraints(lerror,gbc); mainframe.add(lerror); //Add the "Exit" button. bexit = new Button("Exit"); bexit.addActionListener(this); buildconstraints(gbc,2,2,1,1,0,100); gbc.anchor = GridBagConstraints.EAST; gbc.fill = GridBagConstraints.NONE; gridbag.setConstraints(bexit,gbc); mainframe.add(bexit); mainframe.show(); // Show the window. } public void actionPerformed(ActionEvent ae) { // Because this method is handling events from both the buttons // and the text fields, "theSource" variable will sometimes be a button // and sometimes a text field. Because text fields don't have a get // label method, you have to be very careful not to throw a run-time // exception. Maybe you should just break this into separate classes? // The only reason I don't is so that the executable will be all in one // file instead of spread across several tiny ones. Object theSource = ae.getSource(); boolean exitbutton = false; boolean abortbutton = false; boolean didabort = false; // Check if it is the exit button. You always honor exit requests. if(theSource instanceof Button) { if( ((Button)theSource).getLabel().equals("Exit")) { exitbutton = true; mainframe.dispatchEvent( new WindowEvent(mainframe , WindowEvent.WINDOW_CLOSING) ); } // You need to synchronize because what happens if // t3 finishes and tells t2 to go on its merry way // before t2.interrupt() is issued. It that // happens you'll issue the interrupt after it is // too late to abort and the interrupt will be // buffered and will rear its ugly head on t2's synchronized(this) // wait() for the User which will throw an { // exception. if( ((Button)theSource).getLabel().equals("Abort")) { // You don't have to check toolatetoabort because the "Abort" // label is synchronized with toolatetoabort. t2.interrupt(); // So long as the abort window is open, let Java // buffer the interrupt request. It will send it // to t2 the next time it waits which abortbutton = true; didabort = true; } } } if(!exitbutton && !abortbutton) { // Don't come in this loop with an exit button // or an abort button. if(theSource instanceof Button) { if( ((Button)theSource).getLabel().equals("Name")) { synchronized(this) // Atomically set both. { setquery(new String(tfaddress.getText())); setsource(new String("Address")); } } if( ((Button)theSource).getLabel().equals("Address")) { synchronized(this) { setquery(new String(tfname.getText())); setsource(new String("Name")); } } } if(theSource instanceof TextField) { synchronized(this) { setquery(new String( ((TextField)theSource).getText() )); setsource(new String( ((TextField)theSource).getName() )); } } synchronized(this) { if(!gettoolatetoabort()) // Atomically, interrupt t2. Without the { // synchronization, t3 could return in the t2.interrupt(); // interim and close the abort window. didabort = true; // Keep the synchronization short. } } if(query.length() == 0 ) // Don't bother with a lookup { if(getsource().equals("Address")) lerror.setText("No Address Specified."); else lerror.setText("No Name Specified."); tfname.setText(""); tfaddress.setText(""); } else // Do bother with a lookukp { if(getsource().equals("Address")) tfname.setText(""); // Clear the opposite field so you won't else // have a miss-match during the lookup. tfaddress.setText(""); // This next section is key. You should never begin a new lookup // unless both t1 and t2 are waiting on the user. For a normal // sequence of lookups, t1 and t2 should have had enought time to // get back to the waiting state. // HOWEVER, if the user aborts, you MUST wait around for both t1 // and t2 to get back to the state where they are waiting on new // input. while( (!gett1waitingOnUser()) || (!gett2waitingOnUser()) ) { try { Thread.sleep(100); // Make sure they are both ready. // I've watched, and generally, this // loop is only executed once, and I don't // think a tenth of a second is much time // to wait for an abort to complete. So, // I'm satisfied with a poling solution. } catch(InterruptedException e) { e.printStackTrace(); } } if(didabort) { lerror.setText("User abort."); try { Thread.sleep(500); // Show the "User abort." message for 1/2 } // a second. catch(InterruptedException e2) { e2.printStackTrace(); } } synchronized(this) { if(gett1waitingOnUser() && gett2waitingOnUser()) // Are they still { // waiting? Should be! this.notifyAll(); // Both t1 and t2 are blocking on the this object. // This will cause both to run which is what // you want. Note: See managelookup() for // how you get them both to time when they // stop. } else { System.out.println("It should be impossible to reach here!" + " But you never know."); } } } } } public void windowClosing(WindowEvent e) //This is how to shut it down. { //This is the part that extends System.exit(0); //WindowAdaptor. } public void windowOpened(WindowEvent e){} // Have to have all the public void windowClosed(WindowEvent e){} // window listeners in public void windowIconified(WindowEvent e){} // order for Java to let us public void windowDeiconified(WindowEvent e){} // say we have "implemented" public void windowActivated(WindowEvent e){} // the windowListener. public void windowDeactivated(WindowEvent e){} public void managelookup() { boolean aborted = false; while(true) { synchronized(this) { t2waitingOnUser = true; // Atomically set and start waiting. try { this.wait(); // waiting for the user to press a button or hit } // enter. catch(InterruptedException e) { e.printStackTrace(); } // Atomically end wait and set these vars. t2waitingOnUser = false; // No longer waiting. toolatetoabort = false; // The window for aborting has just opened. bexit.setLabel("Abort"); // Sync the "Abort" with the // toolatetoabort variable. } // Now that the user has pressed a button or hit enter, spawn a // thread to take care of doing the lookup. aborted = false; // re-initialize after every loop. synchronized(this) { t3 = new Thread(h); // Spawn a thread to do the lookup. t3.start(); // I'm spawning so I can easily do an abort if // the user so requests. try { this.wait(); // Wait for t3 to notifyAll(); // "wait()" releases the synchronize() while it is // blocked, of course. } catch(InterruptedException e2) { // If the user presses the "Abort" button, aborted = true; // the ActionEvent processor will interrupt t3. address = null; // Which will result in this InterruptedException t3.stop(); // Being triggered. So, stop t3. t3 = null; } toolatetoabort = true; // Atomically shut the window for aborting. bexit.setLabel("Exit"); // It makes no sense to abort after t3 has } // shutdown. It is the t3's lookup that takes // a long amount of time. // Also, atomically go back to an "Exit" // button. // If you are here, either t3 notify()ed you that it was through, // or the user aborted. // Either way, bring t1 to a halt. // REMEMBER: No threads are blocking on "this" synchronized(this) // at this point in the program. { // The ActionEvents notified them all to start. try // So, it is safe to attach this one temporarily { // so it can wait on t1. // NOTE: t1's call to notifyAll() to re-awaken t1.interrupt(); // this thread is synchronized. So this thread is this.wait(); // guaranteed to already be waiting even though t1 // is interrupted before this thread begins to wait. // ALSO NOTE: my experimentation shows that you // don't have to be very precise with your timing // regarding when you issue your interrupt() // because java will wait and interrupt the next // time the soon-to-be-interrupted thread goes // to sleep() or starts to wait(). } catch(InterruptedException e3) { e3.printStackTrace(); } } // Now that you have halted t1, it is safe to update the screen. if(aborted) { lerror.setText("User abort."); } else { updatescreen(); } // From here, you jump back into the synchronized portion at the // top of this while(true) loop. } } public void dolookup() { String tmp; // Java is slightly broken in 1.1 still when it comes to // doing DNS services. This tmp variable is part of the // workaround. // THE PROBLEM: The problem is that in Java up to version // 1.1 (at least), you cannot be sure that it has actually // done the lookup until you have tried to access the // variables. That's what tmp does. It just accesses the // variables. If you don't access them in this thread, you // won't know where in your program Java will bring your // threads a halt because it finally decides to do the // lookup. You'll be very sorry in this program if you // don't force Java to do the lookup now because you have // programmed abort to work only while this thread is up // and running. If you don't force Java to do the lookup // in this thread, then you will have no way to abort the // lookup when Java finally does decide to do it. try { address = InetAddress.getByName(getquery()); tmp = address.getHostName(); tmp = address.getHostAddress(); } catch(UnknownHostException e) { address = null; // Java only throws this if you have a name and // are looking for the numbers, and they can't be // found. Thus, Java doesn't know the host. // However, if you have the numbers and are // looking for the alphanumeric name but can't // find it, this does not mean the host doesn't // exist. I suppose that's Java's logic. ??? } synchronized(this) // Tell t2 you're done and expire. { this.notifyAll(); // It is crucial that this notify be synchronized // because the user could abort the process at // any time. If you look, you'll see that the // "catch{}" which catches the abort is also // synchronized. You also synchronize for the // usual reasons. } } public void updatescreen() { if(address == null) // If it is null, you don't want to do the { // string operations below. if(getsource().equals("Address")) // Only clear name field if the user tfname.setText(""); // passed you an address, else // and vice versa. tfaddress.setText(""); lerror.setText("Error: " + getquery() + " is unknown."); } else { if( address.getHostName().equals(address.getHostAddress()) ) { // Java sometime returns the number as the name if it fails. if(getsource().equals("Address")) // Only clear name field if the user tfname.setText(""); // passed you an address, else // and vice versa. tfaddress.setText(""); lerror.setText("Error: " + getquery() + " is unknown."); } else { tfname.setText(address.getHostName()); tfaddress.setText(address.getHostAddress()); lerror.setText("Found it."); } } } public void idledraw() // This is the method called by the thread { // responsible for drawing while the other boolean done = false; // thread does the host look up. int count = 0; // Count seconds during look up. synchronized(this) // When you first start running, block. { // There is another wait at the bottom of // the while(true) loop. You spend the rest // of the program there. t1waitingOnUser = true; // Atomically, set and wait. try { this.wait(); } catch(InterruptedException e) { e.printStackTrace(); } t1waitingOnUser = false; // Atomically unset and go. } while(true) // this is the loop for the rest of the program. { count = 0; done = false; //initialize "done". while(!done) { lerror.setText("Looking up ... " + count++); try { Thread.sleep(1000); } catch(InterruptedException e2) { done = true; // NOTE: I did some experiments, and it seems that, unlike the // notify() method which does nothing if at the moment of // notification there are no threads waiting, the interrupt() // method will be buffered by java. When java sees that the // thread is in a state that can be interrupted (e.g., it has // just gone to "sleep()" or has started to "wait()"), then // java will forward the interrupt request. Thus, you don't // have to be dead on when you issue the interrupt to break // out of the "while(true)" above. You just need to know that // it's in the loop and eventually will "sleep()" meaning java // will eventually forward your interrupt request which will // cause this break out. } } synchronized(this) // This is where this thread usually blocks. { this.notifyAll(); // Let t2 know that you are done so it can write // a little to the screen you've been hogging. t1waitingOnUser = true; // Atomically set and wait. try { this.wait(); // I want this notify and wait to be atomic so // that when t2 is awaken, it is guaranteed that // this thread will be blocking and waiting for // an ActionEvent to start the cycle over again. } catch(InterruptedException e3) { e3.printStackTrace(); } t1waitingOnUser = false; // Atomically unset and go. } } } public void run() { if(Thread.currentThread() == t1) idledraw(); else if(Thread.currentThread() == t2) managelookup(); else dolookup(); } public static void main(String args[]) { if( (args.length > 0) && (args[0].equals("--about")) ) { System.out.println("\nWritten by Paul Serice 1997.\n"); System.out.println("This program is released into the public domain" + " except this credit must"); System.out.println("carry forward.\n"); } else { h = new HostConvert(); t1 = new Thread(h); t2 = new Thread(h); t1.setDaemon(true); t1.start(); t2.start(); h.makemainframe(); } } }