Core Java Technologies Tech Tips Tips, Techniques, and Sample Code Welcome to the Core Java Technologies Tech Tips for October 18, 2005. Here you'll get tips on using core Java technologies and APIs, such as those in Java 2 Platform, Standard Edition (J2SE). This issue covers: * Variable Arity Methods * Customizing Resource Bundle Loading with ResourceBundle.Control * Update to Last Month's Tip on Cookie Management With CookieHandler The Variable Arity Methods tip was developed using Java 2 Platform, Standard Edition Development Kit 5.0 (JDK 5.0). You can download JDK 5.0 at http://java.sun.com/j2se/1.5.0/download.jsp. The Customizing Resource Bundle Loading tip was developed using a snapshot release of Java Platform, Standard Edition 6 (Java SE 6). You can download a snapshot release of Java SE 6 at https://jdk6.dev.java.net/ This issue of the Core Java Technologies Tech Tips is written by John Zukowski, president of JZ Ventures, Inc. (http://www.jzventures.com). You can view this issue of the Tech Tips on the Web at http://java.sun.com/developer/JDCTechTips/2005/tt1018.html See the Subscribe/Unsubscribe note at the end of this newsletter to subscribe to Tech Tips that focus on technologies and products in other Java platforms. For more Java technology content, visit these sites: java.sun.com - The latest Java platform releases, tutorials, and newsletters. java.net - A web forum for collaborating and building solutions together. java.com - Hot games, cool apps -- Experience the power of Java technology. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - VARIABLE ARITY METHODS Variable arity methods, sometimes referred to as varargs methods, accept an arbitrary set of arguments. Prior to JDK 5.0, if you wanted to pass an arbitrary set of arguments to a method, you needed to pass an array of objects. Furthermore, you couldn't use variable argument lists with methods such as the format method of the MessageFormat class or the new to JDK 5.0 printf method of PrintStream to add additional arguments for each formatting string present. The old style of using variable argument lists is described in the February 4, 2003 tip "Using Variable Argument Lists" (http://java.sun.com/developer/JDCTechTips/2003/tt0204.html#1). JDK 5.0 adds a varargs facility that's a lot more flexible. To explore the varargs facility, let's look at a JDK 5.0 version of one of the printf methods in the PrintStream class: public PrintStream printf(String format, Object... args) This method was described in the October 2004 Tech Tip, Formatting Output With the New Formatter http://java.sun.com/developer/JDCTechTips/2004/tt1005.html#2 Essentially, the first argument is a string, with place holders filled in by the arguments that follow. The three dots in the second argument indicate that the final argument may be passed as an array or as a sequence of arguments. Note that the three dots are similar to how variable argument lists are specified in C and C++ programming. The number of place holders in the formatting string determines how many arguments there need to be. For example, with the formatting string "Hello, %1s\nToday is %2$tB %2$te, %2$tY" you would provide two arguments: the first argument of type string, and the second of type date. Here's an example: import java.util.*; public class Today { public static void main(String args[]) { System.out.printf( "Hello, %1s%nToday is %2$tB %2$te, %2$tY%n", args[0], new Date()); } } Provided that you pass in an argument for the name, the results would be something like the following: > java Today Ed Hello, Ed Today is October 18, 2005 Autoboxing and unboxing allow you to provide primitive type arguments (as in the following printf method): System.out.printf("Pi is approximately %f", Math.PI, %n); Here, Math.PI is of type double. Autoboxing converts it to an object of type Double. (For more information on autoboxing, see the April 5, 2005 tip "Introduction to Autoboxing" http://java.sun.com/developer/JDCTechTips/2005/tt0405.html#1.) If you create your own methods to accept variable argument lists, only the last argument to the method can be declared as accepting a variable list. You can't put the variable-length argument first. For example, the following method declaration is valid -- it defines a method that has one parameter which accepts a variable number of String arguments: private static void method1(String... args) The variable argument parameter doesn't have to be the only argument. For instance, the following method takes one integer argument followed by an arbitrary number of arguments. private static void method2(int arg, Object... args) { Putting methods that have the two formats shown above into a sample class allows you to call the methods with a different number of arguments: method1("Hello", "World"); method1(args); method2(12, 'a', "Hello", Math.PI, 1/3.0); method2(18, 94.0); The second call shown above is slightly different than the others. It takes the String[] argument that comes into the main() method. Because the varargs facility is just implicit syntax for creating and passing arrays, an array can be passed directly. In this case, the compiler will simply pass the array unmodified. Here's a sample class, MyArgs, that includes the two types of methods: import java.util.*; public class MyArgs { public static void main(String args[]) { method1("Hello", "World"); method1(args); method2(12, 'a', "Hello", Math.PI, 1/3.0); method2(18, 94.0); } private static void method1(String... args) { System.out.println(Arrays.asList(args) + " // " + args.length); } private static void method2(int arg, Object... args) { System.out.println(Arrays.asList(args) + " / " + arg); } } Here's a sample run of MyArgs: > java MyArgs x y z [Hello, World] // 2 [x, y, z] // 3 [a, Hello, 3.141592653589793, 0.3333333333333333] / 12 [94.0] / 18 MyArgs accesses the variable argument list by passing it to the Arrays.asList() method. In other words, the argument is accessed just like an array. Normally, you would do something more like the following in an enhanced for loop: for (String arg: args) { ... } This would allow you to process one element at a time. As you can see, without much programming effort, the varargs feature allows you to define methods that accept a variable number of arguments. For more information about varargs, see "Varargs" (http://java.sun.com/j2se/1.5.0/docs/guide/language/varargs.html). - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - CUSTOMIZING RESOURCE BUNDLE LOADING WITH RESOURCEBUNDLE.CONTROL In the June 17, 2005 Tech Tips "Beyond J2SE 5.0" and "Collaborating With Sun on Java SE 6" (http://java.sun.com/developer/JDCTechTips/2005/tt0617.html), you learned how to get started with Java SE 6. The platform is still pre-beta, but it is far enough along to see where things are headed and to try out some new features. One of the new features gives you more control in the use of resource bundles (final inclusion of the feature is subject to JCP approval). Resource bundles are used for localization of applications. Their use was discussed in the December 14, 2004 tip "Resource Bundle Loading" (http://java.sun.com/developer/JDCTechTips/2004/tt1214.html#1). Basically, resource bundles give you the ability to map different strings to localized resources so that icons or text strings can show images and translations that are understood in different languages. The use of resource bundles is managed by the ResourceBundle class in the java.util package. The basic process of working with a resource bundle is to get the bundle through the getBundle() method of ResourceBundle, as shown in the following program: import java.util.*; public class Test1 { public static void main(String args[]) { Locale locale = Locale.ENGLISH; ResourceBundle myResources = ResourceBundle.getBundle("MyResources", locale); String string = myResources.getString("HelpKey"); System.out.println("HelpKey: " + string); } } The Test1 program gets a bundle called MyResources. It then gets the resource named HelpKey from the bundle, and prints the value of the resource. MyResources can be either a class file: import java.util.*; public class MyResources extends ListResourceBundle { public Object[][] getContents() { return new Object[][] { {"OkKey", "OK"}, {"CancelKey", "Cancel"}, {"HelpKey", "Help"}, {"YesKey", "Yes"}, {"NoKey", "No"}, }; } } or a file named MyResources.properties, with key value pairs: OkKey=OK CancelKey=Cancel HelpKey=Help YesKey=Yes NoKey=No But, what if you want your resource bundle to be an XML file? The java.util.Properties class offers two ways to load files. One way involves reading the key=value type files through the load() method. The second way involves loading properties from XML files through the loadFromXML() method. Thanks to a new inner class of ResourceBundle in Java SE 6, named Control, you can have resource bundles stored as XML files. At this point you might want to install the latest Java SE 6 snapshot release from the Java SE 6 project home page (https://jdk6.dev.java.net/). The ResourceBundle.Control class offers a series of callback methods to be called when ResourceBundle.getBundle() searches for and loads bundles. By providing your own Control class, you can override the default loading and caching behavior. For one such implementation of a custom Control class, you only need to override its getFormats() and newBundle() methods. The getFormats() method would say you support XML as a format, and newBundle() would then look for that bundle. There are helper methods in the base Control class to convert the base bundle names to the actual resource name. Included with the custom ResourceBundle.Control implementation is a subclass of ResourceBundle named XMLResourceBundle. This subclass is used to load the XML file and treat it as a ResourceBundle. Here's the full class description for the Control class and the ResourceBundle: import java.io.*; import java.net.*; import java.util.*; public class XMLResourceBundleControl extends ResourceBundle.Control { private static String XML = "xml"; public List getFormats(String baseName) { return Collections.singletonList(XML); } public ResourceBundle newBundle(String baseName, Locale locale, String format, ClassLoader loader, boolean reload) throws IllegalAccessException, InstantiationException, IOException { if ((baseName == null) || (locale == null) || (format == null) || (loader == null)) { throw new NullPointerException(); } ResourceBundle bundle = null; if (format.equals(XML)) { String bundleName = toBundleName(baseName, locale); String resourceName = toResourceName(bundleName, format); URL url = loader.getResource(resourceName); if (url != null) { URLConnection connection = url.openConnection(); if (connection != null) { if (reload) { connection.setUseCaches(false); } InputStream stream = connection.getInputStream(); if (stream != null) { BufferedInputStream bis = new BufferedInputStream(stream); bundle = new XMLResourceBundle(bis); bis.close(); } } } } return bundle; } private static class XMLResourceBundle extends ResourceBundle { private Properties props; XMLResourceBundle(InputStream stream) throws IOException { props = new Properties(); props.loadFromXML(stream); } protected Object handleGetObject(String key) { return props.getProperty(key); } public Enumeration getKeys() { Set handleKeys = props.stringPropertyNames(); return Collections.enumeration(handleKeys); } } public static void main(String args[]) { ResourceBundle bundle = ResourceBundle.getBundle("Test2", new XMLResourceBundleControl()); String string = bundle.getString("HelpKey"); System.out.println("HelpKey: " + string); } } Included in the custom Control class is a test program of three lines: ResourceBundle bundle = ResourceBundle.getBundle("Test2", new XMLResourceBundleControl()); String string = bundle.getString("HelpKey"); System.out.println("HelpKey: " + string); The important line here is the first one. You must pass your custom control to the getBundle() method. After that, you use the bundle in the same way as any other bundle. Here's what the XML resource bundle file, named Test2.xml, looks like: OK Cancel Help Yes No Running the XMLResourceBundleControl program produces the following output: > java XMLResourceBundleControl HelpKey: Help Not shown in the implementation of ResourceBundle.Control above is the getTimeToLive() and needsReload() methods: public long getTimeToLive(String baseName, Locale locale) public boolean needsReload(String baseName, Locale locale, String format, ClassLoader loader, ResourceBundle bundle, long loadTime) The getTimeToLive() method returns the "time-to-live" value for resource bundles that are loaded under ResourceBundle.Control. Resource bundles are maintained in a cache to speed performance the next time you use them. This means that the second call to load a bundle from the same class loader will find the bundle already loaded and cached. A positive time-to-live value specifies the number of milliseconds that a bundle can remain in the cache without needing to be being revalidated. The default return value of getTimeToLive() is TTL_NO_EXPIRATION_CONTROL, which disables the expiration control. If you don't want a bundle cached, have getTimeToLive() return ResourceBundle.Control.TTL_DONT_CACHE. If 0 is returned, a bundle is cached, but every time getBundle() is called the bundle is revalidated from the last call. To clear the cache, call the static clearCache() method of ResourceBundle. There is an optional ClassLoader argument to clear bundles loaded through a specific class loader. If not specified, the caller's class loader is used. The needsReload() method determines if an expired bundle in the cache needs to be reloaded. A return value of true means the bundle needs to be reloaded, false means it doesn't. You can control if a resource bundle needs to be reloaded by having the Control class override the needsReload() method. For instance, if you always want a bundle to be reloaded, have the needsReload method always return true. If needsReload always returns true, you should also ensure that the getTimeToLive() method returns 0. Otherwise the data will be cached longer than necessary. For additional information on the resource bundle enhancements and other internationalization features in Java SE 6, see the blog "Overview of Java SE 6's internationalization features" from Sun software architect John O'Conner http://weblogs.java.net/blog/joconner/archive/2005/09/overview_of_mus_1.html. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - UPDATE TO LAST MONTH'S TIP ON COOKIE MANAGEMENT WITH COOKIEHANDLER In last month's tip "Cookie Management with CookieHandler" (http://java.sun.com/developer/JDCTechTips/2005/tt0913.html#1), there were two lines of source code in the Cookie class that defined the expired date formats: private static DateFormat expiresFormat1 = new SimpleDateFormat("E, dd MMM yyyy k:m:s 'GMT'"); private static DateFormat expiresFormat2 = new SimpleDateFormat("E, dd-MMM-yyyy k:m:s 'GMT'"); If you run the Fetch program as described in the tip in a locale other than US English, the program won't work. For the program to work properly in a locale other than US English, the locale needs to be explicitly set in the Cookie class as follows: private static DateFormat expiresFormat1 = new SimpleDateFormat ("E, dd MMM yyyy k:m:s 'GMT'", Locale.US); private static DateFormat expiresFormat2 = new SimpleDateFormat ("E, dd-MMM-yyyy k:m:s 'GMT'", Local.US); The archived version of the tip has been updated to reflect this change. . . . . . . . . . . . . . . . . . . . . . . . IMPORTANT: Please read our Terms of Use, Privacy, and Licensing policies: http://www.sun.com/share/text/termsofuse.html http://www.sun.com/privacy/ http://developers.sun.com/dispatcher.jsp?uid=6910008 * FEEDBACK Comments? Please enter your feedback on the Tech Tips at: http://developers.sun.com/contact/feedback.jsp?category=sdn * SUBSCRIBE/UNSUBSCRIBE Subscribe to other Java developer Tech Tips: - Enterprise Java Technologies Tech Tips. Get tips on using enterprise Java technologies and APIs, such as those in the Java 2 Platform, Enterprise Edition (J2EE). - Wireless Developer Tech Tips. Get tips on using wireless Java technologies and APIs, such as those in the Java 2 Platform, Micro Edition (J2ME). To subscribe to these and other JDC publications: - Go to the Sun Developer Network - Subscriptions page, (https://softwarereg.sun.com/registration/developer/en_US/subscriptions), choose the newsletters you want to subscribe to and click "Submit". - To unsubscribe, go to the Subscriptions page, (https://softwarereg.sun.com/registration/developer/en_US/subscriptions), uncheck the appropriate checkbox, and click "Submit". - To use our one-click unsubscribe facility, see the link at the end of this email: - ARCHIVES You'll find the Core Java Technologies Tech Tips archives at: http://java.sun.com/developer/TechTips/index.html - COPYRIGHT Copyright 2005 Sun Microsystems, Inc. All rights reserved. 4150 Network Circle, Santa Clara, California 95054 USA. This document is protected by copyright. For more information, see: http://java.sun.com/developer/copyright.html Core Java Technologies Tech Tips October 18, 2005 Trademark Information: http://www.sun.com/suntrademarks/ Java, J2SE, J2EE, J2ME, and all Java-based marks are trademarks or registered trademarks of Sun Microsystems, Inc. in the United States and other countries.