I can't stress enough the importance of copy-pasting this into your editor of choice, the word wrap is going to make this impossible to dechpher. I'll see what I can do about the site layout.
//lots of imports here
import java.util.*; // name-IP address map
import java.net.*; // several Socket classes and InetAddress
import java.io.*; // IOException and a couple *Writers
import java.awt.*; // GUI elements
import java.awt.event.*; // GUI event handlers
import java.util.regex.*; // regular expression library, used to prevent a minor exploit
// changed the class/file name, class names should usually start with a capital
// subclass java.awt.Frame (a window) so we can be displayed on-screen
// implement WindowListener and ActionListener (from java.awt.event) so we can handle our own events
// implement java.lang.Runnable so we can listen for incoming messages in a seperate thread
// (I've reciently realized multi-threading wasn't actually necessary...)
public class uChat extends Frame implements WindowListener, ActionListener, Runnable {
// NOTE: within source code, I tend to use "us", "we", "our", etc. to refer to the class/program and it's actions. Don't ask why, I don't know.
// network-related vars
InetAddress addrs; // broadcast IP address, declared below
int port = 12121; // communication port number
DatagramSocket sock; // multi-purpose UDP socket
// GUI elements
TextField name; // top text field
TextArea disp; // main text area
TextField input; // bottom text field
Button send; // obvious
Button connect; // not really "connecting" but close enough
Map lookupTable; // keeps track of which user(name) is at which IP address
// constructor, throws any Exception because I was too lazy to list each one that could happen
// NOT A BEST PRACTICE
public uChat() throws Exception {
super("uChat"); // call superclass constructor: Frame(String s); sets window title
// retrieve broadcast IP address, method may (but shouldn't with a numerical address) throw UnknownHostException
addrs = InetAddress.getByName("255.255.255.255");
// initialze Socket, and bind to the port
sock = new DatagramSocket(port);
// initialize the lookup table as a HashMap
lookupTable = new HashMap();
// set up GUI elements
disp = new TextArea("",0,0,TextArea.SCROLLBARS_VERTICAL_ONLY); // no starting text, default height and width, scrollbar display policy
input = new TextField();
name = new TextField();
send = new Button("Send"); // buttons need
connect = new Button("Join"); // labels
// prevent editing the message display box
disp.setEditable(false);
//GUI layout design can be pretty hairy
// a java.awt.Panel is a simple container
// constructor accept java.awt.LayoutManager as an arg, BorderLayout is a subtype
Panel pnl1 = new Panel(new BorderLayout());
// add an anonymous label, followed by our name field and connect button
pnl1.add(new Label("Name:"), BorderLayout.WEST);
pnl1.add(name, BorderLayout.CENTER);
pnl1.add(connect, BorderLayout.EAST);
// the lower Panel, with the input field and send button
Panel pnl2 = new Panel(new BorderLayout());
pnl2.add(input,BorderLayout.CENTER);
pnl2.add(send,BorderLayout.EAST);
// set the our layout and add the panels and display area
setLayout(new BorderLayout());
add(pnl1, BorderLayout.NORTH);
add(disp, BorderLayout.CENTER);
add(pnl2, BorderLayout.SOUTH);
// set up a new thread and pass us as the Runnable
Thread t = new Thread(this);
// start the thread
t.start();
// add us as ActionListener for the main GUI elements
send.addActionListener(this);
connect.addActionListener(this);
input.addActionListener(this);
name.addActionListener(this);
// add us as a WindowListener for ourself
addWindowListener(this);
// display the main window
setVisible(true);
} // end of constructor
// override Frame.setVisible(boolean b) to add some functionallity
public void setVisible(boolean b) {
super.setVisible(b); // call Frame.setVisible() to actually show/hide the window
if(b) { // if we're being shown...
pack(); // ... shrink/grow the window so that each element gets it's 'prefferred size"
Dimension scrn = Toolkit.getDefaultToolkit().getScreenSize(); // get the screen's size (in pixels) and...
// ... calculater a point exactly our size up and left from the bottom corner of the screen and move us there
setLocation((scrn.width-this.getSize().width)/1,(scrn.height-this.getSize().height)/1);
// the result of this is causing the window to appear in the lower-right corner of the screen
}
} // end of setVisible
public static void main(String arg[]) { // this is actually a stub,
// call the constructor (from inside a try block), but don't keep a reference to the created object
// (it'll dislay itself, so we don't need to)
try { new uChat(); }
// catch any Exception that pops up and pass it to the handle(Excpetion) method (it's way below)
catch(Exception e) { handle(e); }
} // end of main
// a beast of a method here, this is from the ActionListener interface
// it's called any time an ActionEvent occurs, like clicking a button or pressing enter in a text field
// it's pretty convoluted. I'll devote an entire post (or more) to explaining it later.
public void actionPerformed(ActionEvent evt) { /* left out for now */ }
// a "flag" to tell the listener thread to stop
boolean die = false;
// the run() method is from the java.lang.Runnable interface
// this code will run in a seperate thread
// it listens for incoming UDP packets, interprets them, and updated the display text area accordingly
// it's fairly complicated to, and will get it's own post later
public void run() {
// this loop does the hard part
while(!die) { /* left out for now */ }
// we've been told to stop, but we have a little clean up to do
try {
sock.close(); // close the socket
} catch(Exception e) { handle(e); }
}
// from the WindowListener interface, this method is called when the user asks the window to close by clicking it's exit button
// it does a little clean-up: announcing to other uChaters that we're leaving
public void windowClosing(WindowEvent evt) {
setVisible(false); // hide the window
// if we're connected
if(connect.getLabel() == "Join") { //only send LEAV if we're JOINed
try {
//build and send packet announcing that we're leaving
byte[] data = "LEAV".concat(name.getText()).getBytes("ISO-8859-1");
DatagramPacket out = new DatagramPacket(data, data.length,addrs,port);
sock.send(out);
} catch(Exception e) { handle(e); } // if an exception occurs, pass it to handle(Exception)
}
// change the flag, telling the listener thread to stop
die = true;
// this frees all resources associated with this window, allowing the event-processing threads to stop
dispose();
// this method explicitly tells the JMV to exit, but isn't needed as all threads should be ready to stop
// System.exit(0);
}
// these are also part of the WindowListner interface, but aren't used by this program
public void windowOpened(WindowEvent evt) { }
public void windowClosed(WindowEvent evt) { }
public void windowIconified(WindowEvent evt) { }
public void windowDeiconified(WindowEvent evt) { }
public void windowActivated(WindowEvent evt) { }
public void windowDeactivated(WindowEvent evt) { }
// all purpose Exception handler.
// NOT REMOTELY A BEST PRACTICE but sufficient for this small program
// static, so it can be accessed from main()
public static void handle(Exception e) {
// set up a pair of writers to convert the Excpetion's stack trace to a string
StringWriter sb = new StringWriter();
PrintWriter out = new PrintWriter(sb);
// dump the stack trace to the above writer and System.out (aka STDOUT, aka the terminal window)
e.printStackTrace(out);
e.printStackTrace(System.out);
// make a new text area, default sizes, with the stack trace (via the writers) as its content
TextArea disp = new TextArea(sb.getBuffer().toString());
// simple frame, set its layout
Frame f0 = new Frame("Oops");
f0.setLayout(new BorderLayout());
// anonymous inner class as event listener for the frame, more in another post
f0.addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent evt) { ((Window)evt.getSource()).dispose(); }
});
// add a label, then add the text area from above
f0.add(new Label("Oops, something went wrong. uC may or may not still work."), BorderLayout.NORTH);
f0.add(disp, BorderLayout.CENTER);
// pack() and show the frame
f0.pack();
f0.setVisible(true);
// if you crash uChat (hint: start another instance while one's running), you'll see this method
// in action. it's not a pretty error message, and would tell your average user absolutely nothing.
// to be honest, this was 75% to shut the compiler up about "uncaught exceptions", 25% for debugging
}
} Hopefully, with what you already know and the comments to help with what you don't, most of that makes at least some sense.
Not sure what I'll cover next... there's 3 or 4 topics that need to be covered. I should probably talk about multi-threading; there's a fair bit of network communication going on (that'll be at least 2 posts); I could go into how the interface goes together; need to touch on inner classes, maybe exception handling.
Anyway, that should be easier to understand than what I'd posted before. I'll update the top-bar page in a couple minutes.
No comments:
Post a Comment