Core Java Technologies Tech Tips Tips, Techniques, and Sample Code Welcome to the Core Java Technologies Tech Tips for March 14, 2006. 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: * Working with Applet Context Streams * The Singleton Pattern Revisited These tips were 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. 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/2006/tt0314.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. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - WORKING WITH APPLET CONTEXT STREAMS Applets are not discussed much these days but with J2SE 1.4, three methods were added to the AppletContext class of the java.applet package. Most people probably didn't even notice the additions, but these methods allow you to do something pretty useful: store data in streams, where each stream is mapped to a named key. The main method for storing the information is setStream(): public void setStream(String name, InputStream stream) When streams are stored, they're associated with a key in a key-value, Map-like structure. The mapping is specific to the codebase of the applet. This means that an applet coming from one host does not have access to streams coming from another host. After streams are stored, you can retrieve them either by getting a single stream or by getting all streams. To get a single stream, you ask for it by name with a getStream() method: public InputStream getStream(String name) You retrieve all streams with the getStreamKeys() method. When you do this, you don't get back a Map. Instead, you get an Iterator of the String names: public Iterator getStreamKeys() After you get the key name of the specific stream you're interested in, use the getStream() method to get that stream. A typical pattern to use here is: Iterator iter = getAppletContext().getStreamKeys(); if (iter != null) { while (iter.hasNext()) { String name = iter.next(); InputStream stream = getAppletContext().getStream(name); // read stream... } } Keep in mind that these are input streams. If you want to work with characters, you must work with character sets. For instance, to save a String object, you get its bytes, and store them in a ByteArrayInputStream in the AppletContext: String message = ...; ByteArrayInputStream bais = new ByteArrayInputStream(message.getBytes("UTF-8")); getAppletContext().setStream("key-name", bais); To read, you then get the stream and pass in the same character set when converting it to an InputStreamReader. After you have a Reader object, you can then read characters, as in the following example: InputStream stream = getAppletContext().getStream("key-name"); InputStreamReader isr = new InputStreamReader(stream, "UTF-8"); BufferedReader reader = new BufferedReader(isr); String line = reader.readLine(); That is really all there is to the entire API. Store a new stream with the setStream() method. Get a stream back with getStream(). Get the set of stream keys with getStreamKeys(). This might look like everything you need to know regarding streams, but how do you remove the stream contents? The answer is: pass in null as the InputStream associated with a specific key. That will remove the contents for the stream from the system. getAppletContext().setStream("key-name", null); Let's look at a demonstration of the API. First, you need to create an HTML file that is the loader for the program. For an applet named StreamsApplet, needing a 200x200 display area, the HTML file needs to include the following applet tag: The StreamsApplet applet provides two text fields for the key-value pair. The key is the lookup name and the value is the InputStream contents to be stored. The applet also displays two buttons. The first button adds a named stream (or updates an existing one). The second button removes the named stream. The applet shows the current set of names in a JList. Here's the code to create the user interface: import javax.swing.*; import javax.swing.event.*; import java.awt.*; import java.awt.event.*; import java.io.*; import java.util.*; public class StreamsApplet extends JApplet { private static final String CHARSET = "UTF-8"; JButton add; JButton remove; JList list; JTextField key; JTextField value; public void init() { JLabel keyLabel = new JLabel("Key"); keyLabel.setDisplayedMnemonic('K'); key = new JTextField(); keyLabel.setLabelFor(key); JLabel valueLabel = new JLabel("Value"); valueLabel.setDisplayedMnemonic('V'); value = new JTextField(); valueLabel.setLabelFor(value); JPanel topPanel = new JPanel(new GridLayout(2,2)); topPanel.add(keyLabel); topPanel.add(key); topPanel.add(valueLabel); topPanel.add(value); add(topPanel, BorderLayout.NORTH); list = new JList(); list.setSelectionMode (ListSelectionModel.SINGLE_SELECTION); JScrollPane pane = new JScrollPane(list); add(pane, BorderLayout.CENTER); add = new JButton("Add/Update"); add.setDisplayedMnemonic('A'); remove = new JButton("Remove"); remove.setDisplayedMnemonic('R'); JPanel bottomPanel = new JPanel(); bottomPanel.add(add); bottomPanel.add(remove); add(bottomPanel, BorderLayout.SOUTH); } } Now let's add the actions for adding and updating streams. The ActionListener behind the Add/Update button needs to get the name and stream contents from their respective text fields, then store them in the AppletContext. After adding the stream, the list of streams should appear in the JList, and the ActionListener should clear the text fields. String keyText = key.getText(); String valueText = value.getText(); try { ByteArrayInputStream bais = new ByteArrayInputStream(valueText.getBytes(CHARSET)); getAppletContext().setStream(keyText, bais); } catch (IOException ioe) { JOptionPane.showMessageDialog(StreamsApplet.this, "Unable to save", "Error", JOptionPane.ERROR_MESSAGE); } updateList(); key.setText(""); value.setText(""); The updateList() method is quite simple. All you need to do is get the list of names and put them in the JList. DefaultListModel model = new DefaultListModel(); Iterator iter = getAppletContext().getStreamKeys(); if (iter != null) { while (iter.hasNext()) { model.addElement(iter.next()); } } list.setModel(model); The ActionListener behind the Remove button simply sets the stream to null for any name in the key text field. Again, the list of names needs to be updated after removal, and the text fields need to be cleared. String keyText = key.getText(); try { getAppletContext().setStream(keyText, null); } catch (IOException ioe) { JOptionPane.showMessageDialog(StreamsApplet.this, "Unable to clear", "Error", JOptionPane.ERROR_MESSAGE); } updateList(); key.setText(""); value.setText(""); That's not quite all that's needed. Selection of a name in the list should show its current value. This is done with a ListSelectionListener. The listener gets the selected value from the JList and then looks up the stream in the applet context. The getStream() method returns null if the name isn't found, however the JList only includes names with matching streams, so that check isn't necessary. String selection = (String)list.getSelectedValue(); try { InputStream stream = getAppletContext().getStream(selection); InputStreamReader isr = new InputStreamReader(stream, CHARSET); BufferedReader reader = new BufferedReader(isr); String line = reader.readLine(); key.setText(selection); value.setText(line); } catch (IOException ioe) { JOptionPane.showMessageDialog(StreamsApplet.this, "Unable to read", "Error", JOptionPane.ERROR_MESSAGE); } Putting this all together gives you a fully working applet that stores named streams in the applet context. Here is the complete source, attaching all the listeners to their respective components: import javax.swing.*; import javax.swing.event.*; import java.awt.*; import java.awt.event.*; import java.io.*; import java.util.*; public class StreamsApplet extends JApplet { private static final String CHARSET = "UTF-8"; JButton add; JButton remove; JList list; JTextField key; JTextField value; public void init() { JLabel keyLabel = new JLabel("Key"); keyLabel.setDisplayedMnemonic('K'); key = new JTextField(); keyLabel.setLabelFor(key); JLabel valueLabel = new JLabel("Value"); valueLabel.setDisplayedMnemonic('V'); value = new JTextField(); valueLabel.setLabelFor(value); JPanel topPanel = new JPanel(new GridLayout(2,2)); topPanel.add(keyLabel); topPanel.add(key); topPanel.add(valueLabel); topPanel.add(value); add(topPanel, BorderLayout.NORTH); list = new JList(); list.setSelectionMode( ListSelectionModel.SINGLE_SELECTION); JScrollPane pane = new JScrollPane(list); add(pane, BorderLayout.CENTER); add = new JButton("Add/Update"); add.setMnemonic('A'); remove = new JButton("Remove"); remove.setMnemonic('R'); JPanel bottomPanel = new JPanel(); bottomPanel.add(add); bottomPanel.add(remove); add(bottomPanel, BorderLayout.SOUTH); ActionListener addListener = new ActionListener() { public void actionPerformed(ActionEvent e) { String keyText = key.getText(); String valueText = value.getText(); try { ByteArrayInputStream bais = new ByteArrayInputStream( valueText.getBytes(CHARSET)); getAppletContext().setStream(keyText, bais); } catch (IOException ioe) { JOptionPane.showMessageDialog(StreamsApplet.this, "Unable to save", "Error", JOptionPane.ERROR_MESSAGE); } updateList(); key.setText(""); value.setText(""); } }; add.addActionListener(addListener); ActionListener removeListener = new ActionListener() { public void actionPerformed(ActionEvent e) { String keyText = key.getText(); try { getAppletContext().setStream(keyText, null); } catch (IOException ioe) { JOptionPane.showMessageDialog(StreamsApplet.this, "Unable to clear", "Error", JOptionPane.ERROR_MESSAGE); } updateList(); key.setText(""); value.setText(""); } }; remove.addActionListener(removeListener); ListSelectionListener selectListener = new ListSelectionListener() { public void valueChanged(ListSelectionEvent e) { String selection = (String)list.getSelectedValue(); try { InputStream stream = getAppletContext().getStream(selection); InputStreamReader isr = new InputStreamReader(stream, CHARSET); BufferedReader reader = new BufferedReader(isr); String line = reader.readLine(); key.setText(selection); value.setText(line); } catch (IOException ioe) { JOptionPane.showMessageDialog(StreamsApplet.this, "Unable to read", "Error", JOptionPane.ERROR_MESSAGE); } } }; list.addListSelectionListener(selectListener); updateList(); } private void updateList() { DefaultListModel model = new DefaultListModel(); Iterator iter = getAppletContext().getStreamKeys(); if (iter != null) { while (iter.hasNext()) { model.addElement(iter.next()); } } list.setModel(model); } } Thanks to Java Plug-in technology (http://java.sun.com/products/plugin/index.jsp), applets can run in a browser on most desktops. To make sure you have the latest Java software and to try some new applets, visit java.com (http://www.java.com). - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - THE SINGLETON PATTERN REVISITED People can be passionate about patterns. There was a lot of feedback on the January 13,2006 Tech Tip titled "The Singleton Pattern" (http://java.sun.com/developer/JDCTechTips/2006/tt0113.html#1). Some of that feedback highlighted the fact that unless a Singleton class is shared through a single class loader it isn't a Singleton. The fact is that classes loaded from different class loaders aren't the same class, even if they have the same name and come from the same package. Why is this important to understand? Because in some environments the use of multiple class loaders is common. For example, Java 2 Platform, Enterprise Edition (J2EE) application servers use multiple class loaders to be able to unload classes when the classes are no longer needed (removing the class loader for a class removes the class from memory). In addition, J2EE application servers use multiple class loaders to segregate classes for security reasons. So unless your Singleton class is shared through a single class loader (such as the system class loader), it isn't a Singleton. . . . . . . . . . . . . . . . . . . . . . . . If you have your own Tech Tip that you would like to share with others, you're encouraged to post it in an appropriate Sun Developer Network forum (http://forum.java.sun.com/index.jspa). 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 2006 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 March 14, 2006 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.