Books & Tools Techniques

Comprehensive coverage of Ruby 1.8 and 1.9

"The New Most Important Ruby Book"
Peter Cooper,
rubyinside.com

Completely updated for Ajax and Web 2.0

"A must-have reference"
Brendan Eich,
creator of JavaScript

Jude

Jude is my Java documentation browser. It combines Sun's definitive javadocs with the easy-to-use format of Java in a Nutshell, and tops it off with easy keyboard-based navigation and full-text searching.

Jude is available for free evaluation.

See the user's guide for more info

Java in a Nutshell

The 5th edition is now out, with complete coverage of Java 5.0!

It includes a fast-paced tutorial on the language, and a compact quick-reference for the core Java API.

Java Examples in a Nutshell

The 3rd edition, updated for Java 1.4

This edition has all-new coverage of the NIO and JavaSound APIs, completely rewritten Servlets and XML chapters, and coverage of new Java 1.4 features (assertions, logging, preferences, SSL, etc.) added througout. A great book for those who like to learn by example. 193 working examples: 21,900 lines of carefully commented code to learn from.

Java 1.5 Tiger: A Developer's Notebook

Amazon incorrectly credits me as the main author on this book. I'm actually the second author: really more of a consultant. This is a good book about all the language changes in the latest version of Java.

Effective Java

I didn't write this excellent book, but I wish I had.

Author Josh Bloch is probably best known for the collections classes in the java.util package. His experience and wisdom are apparent in this book. I learned from it and recommend it highly.

January 29, 2004

0*Infinity==NaN; 1/-Infinity==-0

The Java floating-point types have special values for negative zero, positive and negative infinity, and not-a-number, or NaN. I hacked up a short program (below) to print out a multiplication table for these special values, and also tables for the +, -, /, and % operators as well. For the most part, these special values behave as you'd expect when you do arithmetic with them. But some of the values produce results that may seem suprising. 1/0 produces infinity, of course, but 0/0 produces NaN which may not be obvious at first. (If you divide by zero using an integer type instead of a floating point type, you get an ArithmeticException. Floating-point arithmetic never throws exceptions in Java.)

The code is below. Cut, paste, compile and run..

public class Arithmetic {
    static final double negzero = 1/Double.NEGATIVE_INFINITY;

    static double[] operands = new double[] {
	0.0, negzero, 1.0,
	Double.POSITIVE_INFINITY, Double.NEGATIVE_INFINITY, Double.NaN
    };

    // Print a double value as a string.  Java would do this conversion 
    // for us, but we want "Inf" instead of "Infinity", which is too
    // wide for tab-separated tables.
    static void print(double x) {
	if (Double.isNaN(x)) System.out.print("NaN\t");
	else if (Double.isInfinite(x)) {
	    if (x < 0) System.out.print("-");
	    System.out.print("Inf\t");  // "Infinity" is too long
	}
	else if (x == 0) {
	    if (1/x == Double.NEGATIVE_INFINITY) System.out.print("-");
	    System.out.print("0.0\t");
	}
	else System.out.print(x + "\t");
    }


    // Print the header row for each table
    static void printHeader(String operator) {
	System.out.println();
	System.out.print(operator + "\t");
	for(int col = 0; col < operands.length; col++) print(operands[col]);
	System.out.println();
    }

    // print out all the tables.
    public static void main(String[] args) {
	printHeader("+");
	for(int row = 0; row < operands.length; row++) {
	    print(operands[row]);
	    for(int col = 0; col < operands.length; col++) 
		print(operands[row] + operands[col]);
	    System.out.println();
	}


	printHeader("-");
	for(int row = 0; row < operands.length; row++) {
	    print(operands[row]);
	    for(int col = 0; col < operands.length; col++) 
		print(operands[row] - operands[col]);
	    System.out.println();
	}

	printHeader("*");
	for(int row = 0; row < operands.length; row++) {
	    print(operands[row]);
	    for(int col = 0; col < operands.length; col++) 
		print(operands[row] * operands[col]);
	    System.out.println();
	}

	printHeader("/");
	for(int row = 0; row < operands.length; row++) {
	    print(operands[row]);
	    for(int col = 0; col < operands.length; col++) 
		print(operands[row] / operands[col]);
	    System.out.println();
	}

	printHeader("%");
	for(int row = 0; row < operands.length; row++) {
	    print(operands[row]);
	    for(int col = 0; col < operands.length; col++) 
		print(operands[row] % operands[col]);
	    System.out.println();
	}
    }
}

January 27, 2004

MyDoom mailbox cleanup as a learning experience

When I chose my PopClean.java mailbox cleaner utility as the subject of the first entry of my new blog, I didn't think I'd be using it again just a few days later....

A good spam filter will delete these messages for you, but if you do your filtering on the client side, then you still have to download all the spam. My utility is based on the fact that virus-spawned spam has predictable subject lines and sizes. It deletes the messages directly from the POP mailbox without ever downloading them.

If you're going to have to delete a bunch of virus-generated messages from your inbox, you might as well make a learning experience out of it. Here's what I did to clean my mailbox...

Before you go any further, though, know that this is not production code, and you use it at your own risk. It works for me, and it is an educational exploration of Java networking and the POP3 protocol. But it permanently deletes data and if you misuse it (or if it has bugs that I don't know about) you could lose email messages. Instead of just compiling and running the code, I suggest you read it through, learn from it, and then decide if you want to use it.

With that said, here's how I used it. First, I poked noticed that the MyDoom messages I had already deleted had subject lines like "hi", "hello" and "test" (and also in initial capitals and all caps). I telnetted to my POP server to inspect the size of some of these messages and found (from a small sample) that they appeared to be between about 32,000 and 32,500 bytes long.

With that data in hand, I invoked PopClean like this (a single command line wrapped to 2 lines):

java PopClean -host pop.example.com -user dflanagan -pass notreally 
              
-size 32000 -subject "(?i)(hi|hello|test)" -list 

The -size argument tells PopClean to only consider messages longer than 32,000 bytes. (I don't have an option for a maximum size, only a minimum size.) The -subject argument is a java.util.regex.Pattern regular expression. The "(?i)" makes the match case-insensitive.

The -list argument in the command-line above tells PopClean to just list matching (size and subject line) messages. Once I was satisfied that PopClean was matching the messages I wanted and not others, I took the -list argument away. PopClean printed:

Connecting to pop.example.com on port 110 with username dflanagan.
Will delete all messages longer than 32000 bytes
that have a subject matching: [(?i)(hi|hello|test)]
 
Do you want to proceed (y/n) [n]:

I happily responded with 'y', and saw 368 messages that looked like this:

Deleted message 3276: test
Deleted message 3279: hi
Deleted message 3280: TEST
Deleted message 3281: Hello
Deleted message 3283: hi
Deleted message 3289: test
Deleted message 3290: hi
Deleted message 3296: Test
Deleted message 3298: test
Deleted message 3299: Hello

Bliss!

Failure to override

22 months ago, I wrote this article for ONJava.com (part of the O'Reilly Network.

I'd pretty much forgotten about the article, until I got an e-mail today from a New Zealand reader known simply as "Hemi". He was the first of (presumably) several thousand readers to point out an error in this code from the article:

public class LRUCache extends java.util.LinkedHashMap {
    public LRUCache(int maxsize) {
	super(maxsize*4/3 + 1, 0.75f, true);
	this.maxsize = maxsize;
    }
    protected int maxsize;
    protected boolean removeEldestEntry() { 
       return size() > maxsize; 
    }
}

This code is supposed to implement a least-recently-used (LRU) cache on top of the java.util.LinkedHashMap class. Can you spot the error? Hint: the class functions as a cache, but fails the LRU part.

Read on to see what I did wrong and learn how you can avoid this category of errors yourself.

My class attempts, but fails, to override the removeEldestEntry() method of LinkedHashMap. The problem is that I got the signature wrong. The method I wanted to override takes a Map.Entry object as an argument. My method takes no arguments, and therefore does not actually override anything. The point of overriding this method is to prune old entries from the cache. (See the javadoc for LinkedHashMap for details.)

My code looks like it overrides the method, but it doesn't. The upshot is that the cache never purges the least-recently used elements, and just grows and grows.

I should have tested my code.

Now, getting on to the main point of this post: Java 1.5 has a solution to this general problem of failing to override the method you intend to override. The new Java metadata/annotation language feature defined by JSR-175 specifes an @Overrides annotation that solves exactly this problem.

Update: In the proposed final draft for JSR-175, this annotation has been renamed @Override.

If I had been writing the article about Java 1.5, and had annotated my method with @Overrides, then the compiler would have issued an error when it noticed that my method did not, in fact, override anything.

Here's the code as I should have written it, updated for Java 1.5, and still untested:

public class LRUCache extends java.util.LinkedHashMap {
    public LRUCache(int maxsize) {
	super(maxsize*4/3 + 1, 0.75f, true);
	this.maxsize = maxsize;
    }
    protected int maxsize;
    @Overrides protected boolean removeEldestEntry(Map.Entry eldest) { 
        return size() > maxsize; 
    }
}

January 25, 2004

Fun with Java Sound

One of the new chapters I added to the 3rd edition of Java Examples in a Nutshell covers the Java Sound API. That chapter was a lot of fun to research and write.

To the right is an applet I played around with before writing the example that actually made it into the chapter: its a MIDI drum machine. Click your mouse in the applet to give it keyboard focus and then strike keys at random to generate percussion effects. Move the mouse to the bottom of the applet for louder sounds and to the top of the applet for softer sounds. Play around with the keys: only keys whose keycode maps to a MIDI key number between 35 and 81 produce any sound.

You'll need the Java 1.4 plugin installed in your browser for this to work, of course. Don't forget to click on the applet. If you don't it may never get the keyboard events it needs.

Please excuse the lame visuals, and take a look at the surprisingly simple source code.

January 24, 2004

Big Changes in Java 1.5 Alpha

Java 1.5 introduces major new changes to the language.
And there is now an alpha release so you can try them out.

The alpha release doesn't include documentation, but the JSRs that define the language features are in public review at the JCP, which means that you can learn about (and coment on, if you choose) these new features.

First up is JSR 14 which adds parameterized types to the language. This is huge. You can use types like:

Map<String,Integer> map = new HashMap<String,Integer>

The public review draft for this is really old, and slighly out of sync with reality. There ought to be a new version soon.

Next is JSR 201, a catch-all for five smaller, but no less exciting, language changes. They are:

  • enumerated constants
  • a new for() loop for easily iterating through the elements of collections and arrays. Basically a foreach loop, but it isn't called that
  • autoboxing and unboxing: primitive values are "boxed" into wrapper objects as needed, and wrapper objects are unboxed into primitive values as needed.
  • varargs -- methods can have variable numbers of arguments (as long as the variable part of the argument list are all of the same type)
  • the ability to import the static members of a class, such as the constants defined by an enumerated type or the methods of the java.lang.Math class.

I served on the expert group for this JSR. Public review has just begun, so read the public review draft, and let us know if you find problems.

Finally, JSR 175 adds metadata or annotations to the language. This is a cool, but fairly obscure feature. I believe it is of primary interest to J2EE types who want to be able to add things like deployment information directly to classfiles. I also get the impression that this feature is being added to Java now because C# has it. The public review comment period for this JSR has closed, but you can still read the public review draft to learn about it.

January 22, 2004

0xCoffeeCabal

The other day I tried to come up with a Java hexadecimal literal that was cooler (and less tainted by sexism) than the ubiquitous 0xCAFEBABE. 0xCAFED00D is misspelled, and 0xDEADBEEF is not suitable for vegetarians.

So like any good Unix user, I came up with this:

grep -i '^[abcdef][abcdefo]*l\?$' /usr/share/dict/words

(The trailing letter l in the regular expression is legal: it is used in Java literals to signify a long instead of an int.)

My favorites?

I thought it was pretty cool that pair BADE and ACCEDED were both legal hexadecimal literals.

Programmers who like programming with patterns will be happy to know that FACADE is legal hexadecimal.

My favorite, though is C0FFEECABAL. But if you don't like coffee, you might prefer C0C0A.

Can you find a better one? Anyone want to submit examples (and translations) using a non-english dictionary?

January 21, 2004

PopClean.java

PopClean is a simple Java utility program I put together to deal with the flood of viral SoBig.F-generated email messages clogging my POP3 inbox. I have included it as an example in the (newly released) third edition of Java Examples in a Nutshell.

PopClean scans a POP3 inbox and can list or delete messages that are above a size you specify. You can also constrain the list or deletion to messages whose Subject: lines match a given regular expression. It does this without having to download the entire message. Since SoBig messages are ~100Kb each, this is a big win in terms of bandwidth.

I've placed this program in the public domain. It comes with NO WARRANTY, and you have to use it at your own risk. I've used it on two different POP3 servers, but have not tried to test it rigorously. It requires Java 1.4 (for regular expression matching).

I'm sure there are other utilities like this one out there. I wrote this while I was in the middle of updating Java Examples in a Nutshell, however, and rolling my own version just seemed like the right thing to do. In addition to its utility, this program also serves as an example of how easy it is to write networked programs in Java.

The code follows...

import java.io.*;
import java.net.*;
import java.util.*;
import java.util.regex.*;

/**
 * A simple utility program for deleting messages from a POP3 mailbox based on
 * message size and Subject line.  I wrote this to delete the hundreds of 
 * huge SoBig.F mails that I was receiving without having to download them.
 * It has been used on two different POP3 servers, but has not been
 * rigorously tested for compliance with the POP3 protocol.
 *
 * Don't run this program unless you understand what it is doing.  It deletes
 * e-mail without downloading it: YOU MAY PERMANENTLY LOSE DATA!
 * 
 * Typical usage:
 * 1) Look at the subject lines for the big messages you've got
 * 
 *      java PopClean -host host -user user -pass pass -size 100000 -list 
 * 
 * 2) Create a regular expression to match viral subject lines, and use it
 *    to delete large matching messages
 *      java PopClean -host h -user u -pass p -size 100000 \
 *          -subject 'Thank you!|Re: Your application'
 *    This will ask for confirmation before proceeding.
 *
 * 3) If you're confident that all big messages are virus-infected, then
 *    you can skip the -subject argument and delete on size alone
 *      java PopClean -host h -user u -pass p -size 100000
 *    This will ask for confirmation before proceeding.
 *
 * This program was written by David Flanagan.
 * It is placed in the public domain.
 * This program is provided AS-IS, with NO WARRANTY, either express or implied.
 * Use at your own risk.
 */
public class PopClean {
    static BufferedReader in = null;
    static PrintWriter out = null;
    static Socket s = null;
    static boolean debug = false;

    public static void main(String args[]) {
	try {
	    String hostname = null, username = null, password = null;
	    int port = 110;
	    int sizelimit = -1;
	    String subjectPattern = null;
	    Pattern pattern = null;
	    Matcher matcher = null;
	    boolean listonly = false;
	    boolean confirm = true;

	    // Handle command-line arguments
	    for(int i = 0; i < args.length; i++) {
		if (args[i].equals("-user"))
		    username = args[++i];
		else if (args[i].equals("-pass"))
		    password = args[++i];
		else if (args[i].equals("-host"))
		    hostname = args[++i];
		else if (args[i].equals("-port"))
		    port = Integer.parseInt(args[++i]);
		else if (args[i].equals("-size")) 
		    sizelimit = Integer.parseInt(args[++i]);
		else if (args[i].equals("-subject")) 
		    subjectPattern = args[++i];
		else if (args[i].equals("-debug"))
		    debug = true;
		else if (args[i].equals("-list"))
		    listonly = true;
		else if (args[i].equals("-force"))  // don't confirm
		   confirm = false;
	    }
	    
	    // Verify them
	    if (hostname == null || username == null || password == null ||
		sizelimit == -1)
		usage();
	    
	    // make user the pattern is a valid regexp
	    if (subjectPattern != null) {
		pattern = Pattern.compile(subjectPattern);
		matcher = pattern.matcher("");
	    }

	    // Say what we are going to do
	    System.out.println("Connecting to " + hostname + " on port " +
			       port + " with username " + username + ".");
	    if (listonly) {
		System.out.println("Will list subject lines for messages " +
				   "longer than " + sizelimit + " bytes");
		if (subjectPattern != null) 
		    System.out.println("that have a subject matching: [" +
				       subjectPattern + "]");
	    }

	    else {
		System.out.println("Will delete all messages longer than "+
				   sizelimit + " bytes");
		if (subjectPattern != null) 
		    System.out.println("that have a subject matching: [" +
				       subjectPattern + "]");
	    }
		
	    // If asked to delete, ask for confirmation unless -force is given
	    if (!listonly && confirm) {
		System.out.println();
		System.out.print("Do you want to proceed (y/n) [n]: ");
		System.out.flush();
		BufferedReader console =
		    new BufferedReader(new InputStreamReader(System.in));
		String response = console.readLine();
		if (!response.equals("y")) {
		    System.out.println("No messages deleted.");
		    System.exit(0);
		}
	    }

	    // Connect to the server
	    s = new Socket(hostname, port);
	    in = new BufferedReader(new InputStreamReader(s.getInputStream()));
	    out = new PrintWriter(new OutputStreamWriter(s.getOutputStream()));
	    // Read the welcome message from the server until we get +OK
	    System.out.println("Connected: " + checkResponse());
	    
	    // Now log in
	    send("USER " + username);
	    send("PASS " + password);
	    System.out.println("Logged in");

	    // Check how many messages
	    String stat = send("STAT");
	    StringTokenizer t = new StringTokenizer(stat);
	    System.out.println(t.nextToken() + " messages in mailbox.");
	    System.out.println("Total size: " + t.nextToken());
	    
	    // Get a list of message numbers and sizes
	    send("LIST");
	    List msgs = new ArrayList();
	    String line;
	    for(;;) {
		line = in.readLine();
		if (line == null) throw new IOException("Unexpected EOF");
		if (line.equals(".")) break;
		msgs.add(line);
	    }

	    // Now loop through the messages one at a time
	    int nummsgs = msgs.size();
	    for(int i = 0; i < nummsgs; i++) {
		String m = (String) msgs.get(i);
		StringTokenizer st = new StringTokenizer(m);
		int msgnum = Integer.parseInt(st.nextToken());
		int msgsize = Integer.parseInt(st.nextToken());

		// If the message is too small, ignore it.
		if (msgsize <= sizelimit) continue;

		// If we're listing messages, or matching subject lines
		// find the subject line for this message
		String subject = null;
		if (listonly || pattern != null) {
		    subject = getSubject(msgnum);  // get the subject line

		    // If we couldn't find a subject, skip the message
		    if (subject == null) continue;

		    // If this subject does not match the pattern, then
		    // skip the message
		    if (pattern != null) {
			matcher.reset(subject);
			if (!matcher.matches()) continue;
		    }

		    // If we are listing, list this message
		    if (listonly) {
			System.out.println("Subject " + msgnum + ": " +
					   subject);
			continue;  // so we never delete it
		    }
		}

		// If we are not listing, then delete the message
		if (!listonly) {
		    send("DELE " + msgnum);
		    if (pattern == null) 
			System.out.println("Deleted message " + msgnum);
		    else 
			System.out.println("Deleted message " + msgnum +
					   ": " + subject);
		}
	    }

	    // When we're done, log out and shutdown the connection
	    shutdown();
	}
	catch(Exception e) {
	    // If anything goes wrong print exception and show usage
	    System.err.println(e);
	    usage();
	    // Always try to shutdown nicely so the server doesn't hang on us
	    shutdown();
	}
    }

    public static void usage() {
	System.err.println("java PopClean <options>");
	System.err.println(
"Options are:\n" +
"-host <hostname>  # Required\n" +
"-port <port>      # Optional; default is 110\n" +
"-user <username>  # Required\n" +
"-pass <password>  # Required and sent as cleartext; APOP not supported\n" +
"-size <limit>     # Message size in bytes. Shorter messages are ignored.\n" +
"-subject <regexp> # Optional java.util.regex.Pattern regular expression\n" +
"                  # only messages with a matching Subject line are deleted\n"+
"-list             # List matching subjects instead of deleting messages\n" +
"-force            # Don't ask for confirmation before proceeding\n" +
"-debug            # Display POP3 protocol requests and responses\n");

	System.exit(1);
    }

    public static String send(String cmd) throws IOException {
	if (debug) System.out.println(">>>" + cmd);
	out.print(cmd);
	out.print("\r\n");
	out.flush();
	String response = checkResponse();
	if (debug) System.out.println("<<<+OK " + response);
	return response;
    }

    public static String checkResponse() throws IOException {
	String response;
	for(;;) {
	    response = in.readLine();
	    if (response == null) 
		throw new IOException("Server unexpectedly closed connection");
	    else if (response.startsWith("-ERR"))
		throw new IOException("Error from server: " + response);
	    else if (response.startsWith("+OK"))
		return response.substring(3);
	}
    }

    public static String getSubject(int msgnum) throws IOException {
	send("TOP " + msgnum + " 0");
	String subject = null, line;
	for(;;) {
	    line = in.readLine();
	    if (line == null) throw new IOException("Unexpected EOF");
	    if (line.startsWith("Subject: ")) subject = line.substring(9);
	    if (line.equals(".")) break;
	}
	return subject;
    }

    // Disconnect nicely from the POP server.
    // This method is called for normal termination and exceptions.
    public static void shutdown() {
	try {
	    if (out != null) {
		send("QUIT");
		out.close();
	    }
	    if (in != null) in.close();
	    if (s != null) s.close();
	}
	catch(IOException e) {}
    }

}

Advertising
About
Store
Search
Google
Web this site
Archives
Syndicate

Powered by
Movable Type