Formattable
You can format your own custom classes by implementing the Formattable interface. In format strings, you use %s and %S to indicate where you want your custom class instances placed. The default formatting for objects matched to %s and %S is simply whatever is returned by toString( ). However, more often than not, this is just a debugging string not really meant for display to end users. If your class implements the java.util.Formattable interface, Java will use the return value of the object's formatTo( ) method instead. That method has this signature:
public void formatTo(Formatter formatter, int flags, int width, int precision)
The four arguments are:
formatter
The Formatter that called formatTo. More importantly, this is the object onto which the output will be written. Your method will invoke this object's format( ) methods to write to the underlying stream.
flags
A bitmasked constant providing the values of various flags set for this operation: ^, -, #, etc. You interpret these with the FormattableFlags class.
width
The minimum number of characters your method must return. If the returned value has fewer characters than the specified minimum, it will be padded with spaces.
precision
The maximum number of characters your method should return.
Earlier, I complained that the uppercasing in the URL class was too naïve because, when formatted, it changed the case of case-sensitive parts such as the path and the query string as well as case-insensitive parts such as the scheme and the hostname. There's another problem with the naïve uppercasing: the scheme and hostnames are defined in ASCII, but uppercasing isn't always. In particular, uppercasing the letter i in Turkey produces the capital dotted I. rather than the usual undotted capital I. For instance, www.microsoft.com uppercases as WWW.MI.CROSOFT.COM, which will not resolve.
Example 7-5 demonstrates a FormattableURL class that uppercases only those parts of a URL that can be uppercased without changing its meaning. Ideally this would be a subclass of java.net.URL, but since URL is final, delegation is used instead. In essence, FormattableURL is a wrapper around a URL object that just provides the formatTo( ) method.
Example 7-5. Implementing Formattable
import java.util.*; import java.net.*; public class FormattableURL implements Formattable { private URL delegate; public FormattableURL(URL url) { this.delegate = url; } public void formatTo(Formatter formatter, int flags, int width, int precision) { if (precision < -1) { throw new IllegalFormatPrecisionException(precision); } if (width < -1) { throw new IllegalFormatWidthException(width); } if (precision > width) { throw new IllegalFormatPrecisionException(precision); } // Check to see if we've been asked to use any flags we don't interpret int recognizedFlags = FormattableFlags.UPPERCASE | FormattableFlags.LEFT_JUSTIFY; boolean unsupportedFlags = ((~recognizedFlags) & flags) != 0; if (unsupportedFlags) { // We should really pass the flags to this constructor. // However, Java doesn't offer any reasonable way to get these. throw new IllegalFormatFlagsException("#"); } boolean upperCase = (flags & FormattableFlags.UPPERCASE) != 0; StringBuffer sb = new StringBuffer( ); String scheme = delegate.getProtocol( ); if (upperCase && scheme != null) { scheme = scheme.toUpperCase(Locale.ENGLISH); } String hostname = delegate.getHost( ); if (upperCase && hostname != null) { hostname = hostname.toUpperCase(Locale.ENGLISH); } String userInfo = delegate.getUserInfo( ); int port = delegate.getPort( ); String path = delegate.getPath( ); String query = delegate.getQuery( ); String fragment = delegate.getRef( ); if (scheme != null) { sb.append(scheme); sb.append("://"); } if (userInfo != null) { sb.append(userInfo); sb.append("@"); } if (hostname != null) { sb.append(hostname); } if (port != -1) { sb.append(':'); sb.append(port); } if (path != null) { sb.append(path); } if (query != null) { sb.append('?'); sb.append(query); } if (fragment != null) { sb.append('#'); sb.append(fragment); } boolean leftJustify = (flags & FormattableFlags.LEFT_JUSTIFY) != 0; // Truncate on the right if necessary if (precision < sb.length( )) { sb.setLength(precision); } else {// Pad with spaces if necessary while (sb.length( ) < width) { if (leftJustify) sb.append(' '); else sb.insert(0, ' '); } } formatter.format(sb.toString( )); } } |
The formatTo( ) method first checks to see if the values passed make sensethat is, that the width is greater than or equal to the precision, and both are greater than or equal to -1. (-1 simply indicates that these values weren't set.) Assuming these checks pass, it splits the delegate URL into its component parts and uppercases the two case-insensitive parts (the scheme and the hostname) if the uppercase flag is set. It then appends all the other parts without changing their cases at all. Finally, if the precision is less than the string's length, the formatted string is truncated on the right. If the string's length is less than the specified width, the string is padded with spaces: on the right by default but on the left if the left-justified flag is set. If any other flags are present, an IllegalFormatFlagsException is thrown. Thus, it becomes possible to format a URL like this:
URL url = new URL("http://www.example.org/index.html?name=value#Fred"); System.out.printf("%60.40S ", new FormattableURL(url));