Programming 101 By Way of Java

Programming 101


By Way of Java


Instructor: T. Boutell


Transcript for 1/16/97



Stimulants Anonymous

Welcome to the Java programmers' roasteria. A wide variety of stimulating beverages are consumed among the whirring virtual machines.

You say, "welcome to the tenth meeting of the java class."
You say, "over the last two weeks, we've spent a lot of time talking about the chat server."
You say, "actually, there are two programs in our 'talk' package: a client and a server."
You say, "the client is an applet, made up of the two classes 'Client.java' and 'ChatFrame.java'."
You say, "the server is a standalone java application, made up of the classes 'Server.java' and 'Connection.java'."
You say, "by now, we've looked at all of these classes, except for the Connection class."
You say, "you'll find that the Connection class is actually quite similar to the Client class. That's because they were made to talk to one another. I copied a great deal of code from one to the other when I wrote the client and server."
You say, "please open the following URL: http://boutell.com/~boutell/class/talk.html"
You say, "and then select the link to Connection.java in order to see the code we'll be talking about."
You say, "now, notice that a lot of the members of this class are also found in Client.java. It's actually much the same, just without the user interface gadgets that are in the Client class."
You say, "we have a Socket, on which we talk to our 'peer' (the client)."
You say, "we have DataInputStream and DataOutputStream objects, through which we read from and write to the socket elegantly."
You say, "now, we also have two threads..."
You say, "one thread's job is to read String objects coming through the socket from the client."
You say, "that's the inputThread object."
You say, "the other thread's job is to write String objects *to* the client."
You say, "let's look at the inputRun method, which is executed by the input thread."
You say, "the first thing we do here is create the input stream:"
You say, "inputStream = new DataInputStream("
You say, "socket.getInputStream());"
You say, "as I've mentioned, DataInputStream and DataOutputStream are useful classes because they can write 'int', 'float', 'String' and a few other types in an efficient and portable way."
You say, "the first string that comes from the client is the user's name. So we fetch that here:"
You say, "name = inputStream.readUTF();"
You say, "readUTF could have been called readString, but they wanted to be fancy and refer to the way the string gets encoded. We don't really care as long as writeUTF and readUTF can talk to each other no matter what operating systems are involved; and that's what they deliver."
You say, "now, once we know who the user is, we announce that to the other users."
You say, "the outputLine method of the Server class takes care of sending things to other people."
You say, "we generally don't want to bother sending a message to the same person who sent it, so we pass a reference to this connection as the first argument."
You say, "the Server.outputLine method can then make sure not to send that particular string back to the client it came from."
You say, "here we announce the user:"
owner.outputLine(this, "* * * " + name + 
	" has connected.");
You say, "now we go into a loop, reading a string at a time and forwarding it to all of the user users by calling owner.outputLine."
You say, "now, I know threads can be confusing, but consider how nice this is. In a server like this, lots of things happen at once -- many different users want to say something, and many other users have to hear it. Every user's connection goes at its own pace, and we have to keep all of those sockets happy."
You say, "thanks to java's threads, we can tackle these tasks separately, by designing a thread to take care of each one."
jsam nods.
You say, "now, the other thread's job is to send output to the user as needed."
You say, "take a look at the outputRun method..."
You say, "this method creates a DataOutputStream associated with the socket."
You say, "then, in an endless loop, it calls the function 'outputStep()', which takes care of any strings that are waiting to go out. Let's move on to that method..."
You say, "flushOutputStrings();"
You say, "this calls a method that does the dirty work of writing the strings to the stream, and removing them from the Vector of strings, until there are no more."
You say, "next, we enter a synchronized block, in which we signal our willingness to wait patiently until more strings are available. This is important to avoid running the CPU ragged by constantly checking for work to be done."
You say, "here's the code for the block:"
You say, "synchronized (outputStrings) {"
You say, "... now we have exclusive access to the vector of strings. If there is no work to be done, then we're ready to signal our willingness to wait..."
You say, "if (outputStrings.size() == 0) {"
You say, "... this check is important to avoid falling asleep if strings arrived between our call to flushOutputStrings and the start of the synchronization block..."
You say, "outputStrings.wait();"
You say, "... and this call signals our willingness to wait."
You say, "I mentioned that when we are inside a synchronized block, we have exclusive access to the object we synchronized on."
jsam says, "Tom, a quick question: does a method with no 'throws' clause promise never to throw any exceptions?"
You say, "sam: it promises never to throw any exceptions that a reasonable application is expected to catch."
You say, "sam: OutOfMemoryException could still happen, for instance, but there's little to be done about that."
jsam says, "what kind of promise is it? enforced by the compiler?"
You say, "sam: yes."
jsam says, "so there's a small list of exceptions that can get through. okay, I shall look that up later."
Tom nods. "Cool."
You say, "now, when we call wait(), we are voluntarily giving up our exclusive access to the object and falling asleep for a while."
You say, "we won't wake up again until another thread gets exclusive access to the same object and calls notify()."
You say, "sure enough, that happens in the inputLine method; scroll down a bit..."
synchronized (outputStrings) {
	outputStrings.addElement(s);
	outputStrings.notify();
}
You say, "this method is called from the Server.outputLine method; it calls inputLine on each connection to let them know a new string needs to be sent."
You say, "now, this method doesn't send the string right away, because the thread that's calling this method shouldn't have to sit around and wait for that. Otherwise a user with a good connection might be stuck waiting for a user with a terrible one."
You say, "instead, we do something nifty: we get exclusive access to the outputStrings vector, we add the string to the list, and we call notify()."
You say, "and when we do that, the output thread -- which is wait()ing patiently for us -- comes back to life and takes care of the work to be done."
jsam says, "so 'x.notify()' relinquishes access to x ?"
You say, "sam: yes, it does. When we notify, we are giving up our access, which gives any wait()ing threads a chance to wake up."
jsam nods.
You say, "now, I covered a lot of this two weeks ago when I discussed the client, but it's important and confusing stuff, so I felt it was worth reviewing."
You say, "and now we've examined all of the chat client and server source code."
You say, "I'd like to give you a chance to ask questions about it before we move on to something else."
jsam says, "what determines termination of a java application?"
You say, "sam: good question. If the application doesn't start any threads, it'll exit when main() ends."
You say, "sam: if it starts threads, the application should exit when you stop any threads that haven't been marked as 'daemon' threads. The Thread class documentation has more information about that."
stevi says, "if there is more than one thread waiting, how is priority determined?"
You say, "stevi: a very good question..."
You say, "stevi: first of all, you can give threads different priority levels. The Thread class docs cover that too. You can arrange thread priorities to suit your purposes."
stevi nods.
You say, "stevi: but, second, java doesn't lay down hard and fast rules for how thread scheduling should work. So if you want to make Darn Sure the right thread gets something, you'll need to code carefully for that by synchronizing on the right thing, or perhaps using a variable or two to test whether it's really 'your turn.'"
stevi nods again.
You say, "stevi: for instance, a particular operating system might ensure that low priority threads run half as often as high priority ones, but not that a low priority thread would always lose a race to start running next."
You say, "stevi: and, just for amusement's sake, some computers do have more than one processor. So you might have several threads truly running at the same time. In that case, it's pretty much pure chance who will stop wait()ing first."
You say, "whee."
stevi says, "the thread lottery!"
jsam grins.
jsam says, "A friend of mine did a very good job of demonstrating why not to make dramatic assumptions about thread scheduling in an Ada practical; it turned out that the cheap compiler we were using didn't do any pre-emption of threads."
jsam says, "presumably busy loops are similarly a bad idea in java threads."
You say, "sam: sure. Netscape doesn't really pre-empt threads under some circumstances too."
You say, "sam: that's why it is critical to call sleep() for short periods of time if you're doing something that doesn't naturally wait a lot, like these input and output threads do."
You say, "I'm pleased to say that the next applet we'll be looking at does not involve any sockets or threads whatsoever, and still manages to be interesting."
You say, "now, we've worked with a lot of the basic user interface components available in java."
You say, "we've worked with buttons, text areas, labels and so on."
You say, "the most important component we've avoided so far is probably the Scrollbar class."
You say, "please fire up the following URL:"
You say, "http://boutell.com/~boutell/class/map/MapApplet.html"
stevi has Civilization flashbacks.
You say, "whee! Good, it's working properly then."
You say, "you should most definitely have Civilization flashbacks."
You say, "the mountains and hills are in the wrong sort of perspective, but you get the idea, no doubt."
stevi says, "are these Kate McDonnell graphics?"
You say, "yes. Kate did a lovely job."
You say, "okay, let's take a look at the source code for this critter, shall we?"
You say, "the MapApplet class is a straightforward subclass of applet; no wacky 'implements Runnable' this time."
You say, "there are four user interface components that make up the display:"
You say, "two Scrollbar objects (vertical and horizontal), a button (newMap), and a simple subclass of canvas to draw the map in (mapCanvas)."
You say, "you may notice that the map is made up of simple tiles that are used over and over."
You say, "there's an array of Image objects which refer to these:"
You say, "Image tiles[];"
You say, "this array will have seven elements, one for each type of terrain."
You say, "the terrain types are defined next:"
You say, "static final int ocean = 0;"
You say, "static final int plain = 1;"
You say, "... and so on, through 'mountain'."
You say, "each of the tiles is 32 pixels across. We define that in one place so it can be conveniently changed if we decide to ask Kate for different GIFs:"
You say, "static final int tileSize = 32;"
You say, "we define the size of the map the same way, so it can be easily changed without chasing down many numbers in the code:"
You say, "int width = 32;"
You say, "int height = 32;"
You say, "by 'size', we mean how many tiles across and down, not how many pixels."
You say, "the last global variable is an array in which we keep the map:"
You say, "int map[][];"
You say, "this is a two-dimensional array. The first dimension is Y ('rows'), the second dimension is X ('columns')."
You say, "each integer in the array is one of the possible tile type numbers."
You say, "to find the right image to draw, we use that integer as an index into the tiles[] array and pull out the right one."
You say, "now, the init() method does quite a number of things in this class..."
You say, "so I have broken it into several smaller methods."
You say, "this makes it easier to read the code and understand what is going on."
You say, "the makeMap() method is responsible for filling the map array with something reasonable."
You say, "let's take a quick look at that method."
You say, "now, our basic strategy for creating this little world is this:"
You say, "first, we fill the entire map with 'ocean' tiles. Some of them will still be there when we're done."
You say, "next, we place a few 'mountain' tiles, and give them altitudes of 100 'silly units of measurement'."
You say, "notice that we have two additional arrays in this method, altitude and newAltitude. altitude is used to figure out what terrain types would be suitable in a particular location."
You say, "we make several passes over the map, smoothing it out by deciding the altitude of empty cells based on the altitude of their neighbors."
You say, "we use newAltitude as a place to keep track of what the altitudes should be on the next pass. We don't want to make changes in altitude itself as we go along, because the next cell to the right might take that change into account; and that would tend to 'drag' the map in that direction, resulting in higher land to the right and down as the loop continued on that pass."
You say, "now, we need a way to decide when the map is done. I made up a reasonable criteria: '80% full of stuff, or we've made 100 passes.'"
You say, "it is always a good idea to make sure that your code will always exit eventually, no matter what. My '100 passes' rule takes care of that."
You say, "the rule is expressed by this line:"
You say, "while ((full < acceptable) && (passes < 100)) {"
You say, "now, we go into a loop in which we examine every cell..."
for (y = 0; (y < height); y++) {
	for (x = 0; (x < width); x++) {
		newAltitude[y][x] = altitude[y][x];
		if (altitude[y][x] == 0) {
			if (fillNewCell(
				altitude, newAltitude, y, x)) {
				full++;
			}
		}
	}
}
You say, "you might notice that the braces nest pretty deeply here. That's why I've broken out the task of actually filling an empty cell into its own method."
You say, "notice that I pass the altitude and newAltitude arrays to the method. It's perfectly OK to pass arrays, as long as you give their types properly, like so:"
boolean fillNewCell(int altitude[][], int newAltitude[][], int y, int x)
You say, "the compiler will check and make sure that the arrays we're passing really are two-dimensional arrays of integers, just as it would check for any other type."
You say, "now, as I've mentioned before, arrays are objects. That means that when we pass them to a method, that method is able to modify them. If we modified y or x, that would only affect the copy that is local to the method, but if we modify, say, altitude[5][5], we are making a permanent change." You say, "now, within this method, we first loop through the 'neighbor' cells in all eight directions immediately around this cell, and find out what the altitudes are there. We total up those values, and then we average them to get an idea of how high up a new cell ought to be."
You say, "notice that we call yet another method, legal(y, x), to make sure that each cell location we look at is within the map. It is always a good idea to break a problem down into many smaller methods to keep the code readable."
You say, "now, after this loop, we subtract a random amount from the average altitude, and use that as the altitude for the new cell."
You say, "this happens here:"
newAltitude[y][x] = (totalAltitude / fullNeighbors) 
	- randomBetween(0, 20);
You say, "and finally, we use the altitude as a clue to decide what sort of terrain is appropriate for this cell. Again, we add a little bit of a random bias, but basically the highest altitudes become mountains, the next highest become hills, and so on."
You say, "now, I'm not suggesting that any of this is good geophysics."
You say, "but it does make for a halfway reasonable looking map."
You say, "the randomBetween and legal methods follow this method in the code. We've seen randomBetween before. 'legal' just checks to make sure y, x is a legitimate location in the map."
Malus says, "And it does make some very pretty maps."
You say, "heh."
jsam smiles.
You say, "now, makeComponents should look very familiar, because this class uses a GridBagLayout just like most of the classes we've examined."
You say, "the first component in the layout is a MapCanvas. We define this class later in the source code."
You say, "the second component is the vertical scrollbar. This is on the same row with the map canvas, but just uses VERTICAL fill instead of BOTH."
You say, "the third component is the horizontal scrollbar, which uses HORIZONTAL fill and occupies its own row."
You say, "finally, the fourth component is the 'New Map' button, which is two columns wide and occupies its own row also."
You say, "fortunately, it turns out that a layout like this is very effective for a canvas and its scrollbars."
You say, "the only new thing in this method, really, is at the end:"
You say, "layout();"
You say, "this method asks the browser to lay out the controls now."
You say, "you might wonder why this is necessary, since it happens automatically after init() returns."
You say, "the reason is that, as it turns out, some of the Scrollbar methods do not work properly until after the actual control has been laid out."
You say, "now, if you're afraid it should be obvious why this is so, don't worry. It's not obvious -- it's a bug in a lot of java implementations. All of the Scrollbar methods should work fine after you call the constructor."
You say, "fortunately, I've already suffered through this on your behalf, so when you want to use scrollbars, feel free to cannibalize this applet at great length."
jsam says, "this is a call of an applet method 'layout', not the local variable 'layout', right?"
You say, "sam: that's correct."
You say, "after calling layout(), we call setScrollbars(), which is the next method. So let's take a look:"
You say, "Rectangle r = mapCanvas.bounds();"
You say, "this fetches the bounding box of the map canvas. We want this so we can get the width and the height, in order to decide how big the 'thumb' in the scrollbar should be, for instance."
You say, "int wpixels = width * tileSize;"
You say, "this determines the width of the entire map, in pixels. The map is bigger than the small virtual 'window' into it that we are displaying to the user."
You say, "int hpixels = height * tileSize;"
You say, "this does the same for the height of the entire map."
You say, "now, we're ready to call setValues on the Scrollbar objects. setValues takes four arguments, which represent the 'current position' for the thumb, the size of the thumb (in the same units, whatever they may be), the lowest possible value for the thumb, and the highest possible position for the thumb."
You say, "now, in our case, we are keeping the 'upper left corner' of the area the user can currently see in the variables xoffset and yoffset."
You say, "since we're using pixels for our scrollbar units, the size of the thumb should simply be the width of the map canvas, in pixels."
You say, "the lowest possible coordinate for both scrollbars is zero. The highest possible coordinate, on the other hand, is a little tricky."
You say, "you might very reasonably assume that it should be the width or height of the entire map, in pixels."
You say, "but it turns out that the 'highest possible value' should be the value of the 'left' or 'top' edge of the thumb."
You say, "this is spelled out in the documentation of the java.awt.Scrollbar class."
You say, "so, in the case of the horizontal scrollbar, the 'high' value should be wpixels - 1 - width. The extra pixel is subtracted because we don't want the thumb to actually cover, say, pixel 100 if there are 100 pixels numbered from 0 to 99."
You say, "now, our last task to set up the scrollbars should be simple: we want to set the increment by which the scrollbars move when the user moves line-wise, such as by clicking on the arrows, and page-wise, such as by clicking in the scrollbar."
You say, "this task is made a little bit more complex by... yes... another bug!"
You say, "this time the bug is in the Scrollbar class, as implemented in Netscape 3.0 for Windows."
You say, "the bug is simple: there are two functions, setPageIncrement and setLineIncrement. Netscape, in their inimitable brilliance, got them backwards!"
You say, "yes, this was a very silly thing to do."
jsam gives Tom a test suite, except that he already has one.
You say, "fortunately, we can work around it by asking java what environment we are actually running in. Although this is not an elegant thing to do, it does allow us to work around bugs that aren't our fault and get our programs working properly."
You say, "it's also a chance to talk about getProperty, another handy method brought to you by the 'System' class."
You say, "various characteristics of the system that your browser is actually running on are available to your applet. Each of these characteristics has a string that identifies it. When you fetch a property, you get back a string value."
You say, "this is a lot like 'environment variables' in the Unix and DOS worlds. And, in fact, those are available through getProperty in standalone java applications, I believe."
You say, "in applets, there are just a few properties you can fetch. The important ones for us are java.vendor, java.version, and os.name."
You say, "I use these three strings, along with the convenient 'startsWith' method of the String class, to figure out whether I am running Netscape under Windows."
You say, "I then check to see if the version of java being used is 1.1 or higher. Java 1.1 is not available in Netscape yet. I am assuming, reasonably I hope, that this bug will be fixed when Netscape upgrades its java support."
You say, "no, this isn't perfect, but it's the most responsible and reliable test we can make."
You say, "finally, I call the setLineIncrement and setPageIncrement methods, passing the size of a tile as the line increment and the size of the map canvas as the page increment. If the bug was detected, then I switch the two methods."
You say, "now, we've covered quite a lot of ground, and this is a reasonable stopping point; not only that, we're out of time."
You say, "so I'm going to stop here and talk more about this applet next class."
You say, "feel free to ask questions; I'll be here for a little while yet."
Malus claps.
jsam says, "I don't find the scrolling particularly satisfactory."
jsam says, "if I hold down the mouse in an arrow, it scrolls one step (sometimes without a complete redraw) and then the thumb whizzes further along, not scrolling the image until I let go."
jsam says, "(Netscape 3.01, MacOS.)"
You say, "sam: yes, I've noticed that. My impression was that java scrollbars don't respond at all on the mac until you let go of the mouse."
jsam says, "it generally does a partial redraw immediately."
You say, "interesting. I didn't notice the partial redraw."
jsam will try this with cyberdog/mrj 1.0b2.
You say, "in Linux, it works perfectly, in both Netscape and the JDK..."
jsam says, "are you using netscpae 3.0 or 3.01?"
jsam says, "(on your mac)"
stevi hmmms. "On my mac, it clears all the screen but one square, then redraws when i release the button."
You say, "in NS 3, Windows 95, it works reasonably well, except that there are too many wasteful and slow intermediate steps reported."
You say, "interesting. I'll have to try it more on my mac. I believe I have the latest Netscape for mac, since I had to upgrade from 2.0 when I got it not long ago."
jsam feels that there's a hidden race condition somewhere.
You say, "sam: there may be -- or it may be a bug. Scrollbars are pretty badly implemented. I think I have them working about as well as they can be convinced to work. In Java 1.1, I gather, they are much better. You can even have a scrollable container with components in it."
Malus says, "Tom, have you seen Visual Cafe yet? How much of the last example code would you have let a visual code generator do for you if you had been using one? Or do you eschew them?"
You say, "Malus: I have no problem with code generators, really. They are increasingly common. But for java, it is important to look at the results and see if they write good code that uses a layout manager and doesn't make assumptions about absolute sizes."
jsam says, "interesting: quite different bad behavior from MRJ 1.0b2."
You say, "what's it do?"
jsam says, "all the scroll reporting seems to get queued up until mouseup, when it redraws in the new location several times."
jsam says, "It is attempting to do active scrolling, though, which is interesting."
jsam says, "tom, can you make it easier for us to grab the tile gifs please?"
You say, "sam: oh, sure. They are now available on the MapApplet web page."
jsam says, "does Canvas.repaint() go through an update mechanism (lazy) or immediately call paint (eager) ?"
You say, "sam: it goes through an update mechanism; it is supposed to be lazy and even discard excess repaint requests. It doesn't always do that, though."
You say, "sam: that is, it's been known to be flaky, as you've discovered."
jsam says, "There are a few places where that update mechanism could be flawed here; I'll try it with a local applet viewer which I have more control over, if you can put it and its bits in an auto-indexed directory."
jsam says, "but I shall do so after going to get lunch."
jsam smiles.
stevi smooches jsam.
stevi says, "thanks for class, Tom!"
stevi has to run off now.
You say, "g'night all."
jsam says, "yes, thanks, Tom."
Malus says, "Yes, thanks Tom!"
Tom says, "goodnight, everyone."

Next | Up to the Index

Follow us on Twitter | Contact Us

Copyright 1994-2012 Boutell.Com, Inc. All Rights Reserved.