Return-Path: Received: from pacific-carrier-annex.mit.edu by po10.mit.edu (8.9.2/4.7) id WAA25801; Thu, 12 Jul 2001 22:47:27 -0400 (EDT) Received: from hermes.java.sun.com (hermes-db.java.sun.com [204.160.241.157]) by pacific-carrier-annex.mit.edu (8.9.2/8.9.2) with SMTP id WAA09991 for ; Thu, 12 Jul 2001 22:47:27 -0400 (EDT) Message-Id: <200107130247.WAA09991@pacific-carrier-annex.mit.edu> Date: Thu, 12 Jul 2001 19:47:27 PDT From: "JDC Tech Tips" To: alexp@mit.edu Subject: JDC Tech Tips July 12, 2001 Precedence: junk Mime-Version: 1.0 Content-Type: text/plain; charset=us-ascii Content-Transfer-Encoding: 7bit X-Mailer: Beyond Email 2.2 J D C T E C H T I P S TIPS, TECHNIQUES, AND SAMPLE CODE WELCOME to the Java Developer Connection(sm) (JDC) Tech Tips, July 12, 2001. This issue covers: * JTabbedPane * Using Reflection to Test Methods and Classes These tips were developed using Java(tm) 2 SDK, Standard Edition, v 1.3. You can view this issue of the Tech Tips on the Web at http://java.sun.com/jdc/JDCTechTips/2001/tt0712.html - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - JTABBEDPANE JTabbedPane is a Swing class that you can use when you want several GUI components (such as JPanels) to share the same space. Each component is made visible by selecting a tab. Here is a simple example of JTabbedPane use: import javax.swing.*; import java.awt.event.*; public class JTabDemo1 { public static void main(String args[]) { JFrame frame = new JFrame("JTabDemo1"); // handle window close frame.addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) { System.exit(0); } }); // set up panels with buttons JPanel panel1 = new JPanel(); JPanel panel2 = new JPanel(); panel1.add(new JButton("Button in panel 1 in tab 1")); panel2.add(new JButton("Button in panel 2 in tab 2")); // set up JTabbedPane object and add panels JTabbedPane jtp = new JTabbedPane(); jtp.add("Tab 1", panel1); jtp.add("Tab 2", panel2); // display frame.getContentPane().add(jtp); frame.setLocation(200, 200); frame.pack(); frame.setVisible(true); } } The JTabDemo1 program sets up two panels, each with a button. The program adds the panels to a JTabbedPane object, and gives each panel a title such as "Tab 1." When you run the program, you can click on the tabs to switch between panels. There is one tab-component pair selected at any given time. Tabs are referenced by an index between 0 and the number of tabs minus 1. Here's another example of using JTabbedPane: import java.awt.*; import java.awt.event.*; import javax.swing.*; import javax.swing.event.*; public class JTabDemo2 { public static void main(String args[]) { JFrame frame = new JFrame("JTabDemo2"); // handle window close frame.addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) { System.exit(0); } }); // set up panels with buttons JPanel panel1 = new JPanel(); JPanel panel2 = new JPanel(); panel1.add(new JButton("Button in panel 1 in tab 1")); panel2.add(new JButton("Button in panel 2 in tab 2")); // set up JTabbedPane object final JTabbedPane jtp = new JTabbedPane(SwingConstants.BOTTOM); // set up listener for JTabbedPane object jtp.addChangeListener(new ChangeListener() { public void stateChanged(ChangeEvent e) { int index = jtp.getSelectedIndex(); String title = jtp.getTitleAt(index); System.out.println("index = " + index); System.out.println("title = " + title); } }); // add tabs, including tooltips and colors and panels jtp.add("Tab 1", panel1); jtp.addTab("Tab 2", null, panel2, "Tab 2 Tip"); jtp.setForegroundAt(0, Color.blue); jtp.setBackgroundAt(1, Color.red); // display frame.getContentPane().add(jtp); frame.setLocation(200, 200); frame.pack(); frame.setVisible(true); } } The JTabDemo2 program shows some of the additional features you can use with JTabbedPane. In this program, the tabs are displayed at the bottom of the pane instead of the top; a tooltip is added for the second tab (you see the tooltip when you pass the mouse pointer over the second tab); and background and foreground colors are set for the tabs. The program also sets up a listener so that tab selection events are captured. For example, if you select the first tab, you see the following displayed in your console: index = 0 title = Tab 1 You can use keyboard keys to navigate JTabbedPane objects. The left and right arrow keys are used to move between tabs, and the Tab key is used to move from a tab to the underlying component. You can also specify icons for display with the title on each tab. JTabbedPane is similar to CardLayout, which allows you to display one component from a "deck" of components. For more information about JTabbedPane, see the JTabbedPane section in Chapter 12, Lightweight Containers, in "Graphic Java: Mastering the JFC, 3rd Edition Volume II Swing" by David Geary. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - USING REFLECTION TO TEST METHODS AND CLASSES You can use the Java reflection package to find out about Java types from inside a running program. For example, you can get a list of all the method names for a particular class, and display that list. Or you can obtain a java.lang.reflect.Method object that represents a particular method, and use that object as a sort of pointer to the method. Suppose you're learning about reflection. You have an application in which you need to call a specific method on an object. So you code the application like this: import java.lang.reflect.*; class A { public void f(int i) { System.out.println("A.f called i = " + i); } } public class RefDemo1 { public static void main(String args[]) throws Exception { A aref = new A(); // find a method in "A" named "f" // and with a single "int" parameter Method meth = A.class.getMethod("f", new Class[]{int.class}); // invoke the method on the "aref" object meth.invoke(aref, new Object[]{new Integer(37)}); } } The RefDemo1 program creates an A object, and then calls method f on the object, using reflection. This approach certainly works, but it represents a convoluted way to make a simple method call. Instead of using reflection in this example, it would be much simpler to say: aref.f(37); to call the method. If this example isn't a good place to use reflection, then what is? This tip tries to answer this question, by presenting a program example of what might be called an "interpreter" or an "object exerciser." The program reads lines of input from a file or the keyboard. Then based on the data in these lines, the program creates objects and calls methods on them. Here's an example of interpreter input: > new java.lang.String ABC > call toLowerCase abc The first line tells the interpreter to create a new String object, with the string "ABC" as the argument to the constructor. The second line tells the interpreter to call the method "toLowerCase." The second line also tells the interpreter that the method must return "abc" or else an error message is produced. Each method has arguments and a return value specified. After the method is called, the return value specified in the input file is checked against the actual return value. An error is flagged if the values are not equal. Using this approach, it's possible to test methods by simply writing scripts that list method names, arguments to the methods, and the expected return value. How would you implement such a program? It's clear that the user can specify arbitrary classes and methods, whose names exist in string form within the program. These strings somehow need to be mapped into actual classes and methods. That is, if the program has a string with the name of a class, it needs to actually create an instance of this class. The program then needs to find and call methods of this class, given strings specifying method names. Reflection is specifically designed for this kind of programming area. Using reflection, it's possible to create class instances from string names, and then look up and execute methods by name. This dynamic feature of a programming language is sometimes termed "late binding." By contrast, languages such as C and C++ use "early binding," that is class/function names are not kept around at run time. Here is the interpreter program: import java.io.*; import java.util.*; import java.lang.reflect.*; class Interpreter { // input line and list of tokens from line private String input; private List tokenlist; // current class and object of that class private Class currcls; private Object currobj; // execute a line of input public void execLine(String line) { // tokenize the input line and return if line is blank input = line; getTokens(); if (tokenlist.size() == 0) { return; } System.out.println("Executing line: " + input); // get type of line (new or call) and dispatch String type = (String)tokenlist.get(0); if (type.equals("new")) { execNew(); } else if (type.equals("call")) { execCall(); } else { msg("Invalid operator on line"); return; } } // create a new object of a class and // make it the current object private void execNew() { if (tokenlist.size() < 2) { msg("Missing class name"); return; } // load the class if not already done try { currcls = Class.forName((String)tokenlist.get(1)); } catch (ClassNotFoundException e) { msg("ClassNotFoundException in forName"); return; } // get arguments to constructor currobj = null; Class args[] = getArgTypes(2); Object vals[] = getArgValues(2); Constructor ctor; // find the constructor try { ctor = currcls.getConstructor(args); } catch (NoSuchMethodException e) { msg("NoSuchMethodException in getConstructor"); return; } // create a new instance of the object try { currobj = ctor.newInstance(vals); } catch (InstantiationException e) { msg("InstantiationException in newInstance"); return; } catch (IllegalAccessException e) { msg("IllegalAccessException in newInstance"); return; } catch (InvocationTargetException e) { msg("InvocationTargetException in newInstance"); return; } } // call a method for the current object private void execCall() { if (tokenlist.size() < 3) { msg("Missing method or return value"); return; } if (currobj == null) { msg("No current class object"); return; } // get the method name and the arguments to the method String methname = (String)tokenlist.get(1); Object ret = getRet(); Class args[] = getArgTypes(3); Object vals[] = getArgValues(3); Method meth; Object retobj; // find the method in the class try { meth = currcls.getMethod(methname, args); } catch (NoSuchMethodException e) { msg("Method not found"); return; } // invoke the method try { retobj = meth.invoke(currobj, vals); } catch (IllegalAccessException e) { msg("IllegalAccessException in invoke"); return; } catch (InvocationTargetException e) { msg("InvocationTargetException in invoke"); return; } // check return value if any if (ret != null && !ret.equals(retobj)) { msg("Invalid return value from method"); return; } } // tokenize the input line private void getTokens() { tokenlist = new ArrayList(); int strlen = input.length(); int i = 0; for (;;) { while (i < strlen && Character.isWhitespace(input.charAt(i))) { i++; } if (i == strlen) { break; } StringBuffer sb = new StringBuffer(); while (i < strlen && !Character.isWhitespace(input.charAt(i))) { sb.append(input.charAt(i)); i++; } tokenlist.add(sb.toString()); } } // get the return value for a method call private Object getRet() { String s = (String)tokenlist.get(2); if (s.equals("void")) { return null; } else if (isNum(s)) { return new Integer(s); } else { return s; } } // get types of the arguments to a constructor or method private Class[] getArgTypes(int start) { int numargs = tokenlist.size() - start; Class args[] = new Class[numargs]; int j = 0; for (int i = start; i < tokenlist.size(); i++) { String s = (String)tokenlist.get(i); args[j++] = isNum(s) ? int.class : String.class; } return args; } // get the argument values for a constructor or method call private Object[] getArgValues(int start) { int numargs = tokenlist.size() - start; Object args[] = new Object[numargs]; int j = 0; for (int i = start; i < tokenlist.size(); i++) { String s = (String)tokenlist.get(i); args[j++] = isNum(s) ? (Object)new Integer(s) : (Object)s; } return args; } // display an error message private static void msg(String txt) { System.out.println("*** " + txt + " ***"); } // determine whether a string is a number NNN or -NNN private static boolean isNum(String s) { int slen = s.length(); int i = slen >= 2 && s.charAt(0) == '-' ? 1 : 0; for (; i < slen; i++) { if (!Character.isDigit(s.charAt(i))) { return false; } } return true; } } public class RefDemo2 { public static void main(String args[]) throws IOException { Reader r; boolean isterm = false; // use command-line file if present, else standard input if (args.length == 1) { r = new FileReader(args[0]); } else { r = new InputStreamReader(System.in); isterm = true; } BufferedReader br = new BufferedReader(r); Interpreter in = new Interpreter(); // read input lines and dispatch for (;;) { if (isterm) { System.out.print("> "); } String inputline = br.readLine(); if (inputline == null) { break; } in.execLine(inputline); } br.close(); } } The program reads lines from a file if a file name is specified on the command line. Otherwise, the program prompts for input (with a ">" prompt). The program breaks each input line into tokens; tokens are entries in the line separated by white space. The tokens are then stored in a list (tokenlist) as strings. Lines without any tokens are ignored. Input lines of this form: new classname arg1 arg2 ... create a new class instance, which becomes the "current object." You can specify arguments to the constructor. In this case, the reflection mechanism is used to locate and then invoke the appropriate constructor. Arguments of the form NNN or -NNN are considered integers; all other arguments are treated as strings. This approach is sufficient to illustrate the concept, although it would be desirable to handle other argument types such as floating-point. Lines of this form: call methodname returnvalue arg1 arg2 ... invoke a method on the current object, using the method name and types of the arguments (int or String) to locate and invoke an appropriate method in the class represented by the current object. Here's an example input script that might be used to test java.lang.String: new java.lang.String ABC call toLowerCase abc new java.lang.String abc call concat abcdef def new java.lang.String abc call indexOf 2 c Store the script in a file named "script." The run the RefDemo2 interpreter program like this: java RefDemo2 script You should see the following display: Executing line: new java.lang.String ABC Executing line: call toLowerCase abc Executing line: new java.lang.String abc Executing line: call concat abcdef def Executing line: new java.lang.String abc Executing line: call indexOf 2 c If you change the "2" to "3" on the last line, the interpreter program will flag an error as follows, indicating there's a bug either in the implementation of the method (indexOf) or in the test case: Executing line: call indexOf 3 c *** Invalid return value from method *** Here's another example of using an interpreter program. Suppose you have a class that looks like this: public class RefTest { public int sum(int a, int b) { return a + b; } public static int stsum(int a, int b) { return a + b; } } You could write some tests for it using this script: new RefTest call sum 10 4 6 call sum 0 -5 5 call sum 0 0 0 call stsum 10 4 6 call stsum 0 -5 5 call stsum 0 0 0 and run them by saying: javac RefTest.java javac RefDemo2.java java RefDemo2 script You should see: Executing line: new RefTest Executing line: call sum 10 4 6 Executing line: call sum 0 -5 5 Executing line: call sum 0 0 0 Executing line: call stsum 10 4 6 Executing line: call stsum 0 -5 5 Executing line: call stsum 0 0 0 The interpreter program illustrates how reflection is useful in a particular family of applications. These kind of applications accept arbitrary user input and use it to manipulate objects by class and method name within a program. For presentation purposes, this tip simplified some of the details and glossed over some issues. But the program does illustrate the power of reflection to solve a particular type of problem. For more information about reflection, see Section 11.2, Reflection, in "The Java(tm) Programming Language Third Edition" by Arnold, Gosling, and Holmes http://java.sun.com/docs/books/javaprog/thirdedition/ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - CORRECTION In the June 12, 2001 issue of the JDC Tech Tips (http://java.sun.com/jdc/JDCTechTips/2001/tt0612.html), there is a paragraph in the tip "Using Peer Classes With the Java Native Interface" that reads: The destroy method is synchronized to avoid race conditions. The create method is called from the constructor, which can execute in only one thread for a given object. Also the getvalue method is read-only, so there are no issues with synchronization for these two methods. The last sentence of this paragraph is wrong. The getValue method is indeed read-only, but it does need to be synchronized. The reason is simply that getValue may be executing in one thread at the same time that another thread is executing the destroy method. If so, a value of 0 for "peerobj" can be passed to the underlying native method for getValue, possibly resulting in a segmentation violation (crash). This issue is further discussed in section 10.3.1, synchronized Methods, in "The Java(tm) Programming Language Third Edition" by Arnold, Gosling, and Holmes http://java.sun.com/docs/books/javaprog/thirdedition/ Also, in the example presented in that tip, both the Java method destroy() and the native method destroy(long) are synchronized. The synchronized declaration of the native method is redundant, given that it's called from a wrapper method that is itself synchronized. . . . . . . . . . . . . . . . . . . . . . . . - NOTE Sun respects your online time and privacy. The Java Developer Connection mailing lists are used for internal Sun Microsystems(tm) purposes only. You have received this email because you elected to subscribe. To unsubscribe, go to the Subscriptions page http://developer.java.sun.com/subscription/ uncheck the appropriate checkbox, and click the Update button. - SUBSCRIBE To subscribe to a JDC newsletter mailing list, go to the Subscriptions page http://developer.java.sun.com/subscription/ choose the newsletters you want to subscribe to, and click Update. - FEEDBACK Comments? Send your feedback on the JDC Tech Tips to: jdc-webmaster@sun.com - ARCHIVES You'll find the JDC Tech Tips archives at: http://java.sun.com/jdc/TechTips/index.html - COPYRIGHT Copyright 2001 Sun Microsystems, Inc. All rights reserved. 901 San Antonio Road, Palo Alto, California 94303 USA. This document is protected by copyright. For more information, see: http://java.sun.com/jdc/copyright.html - LINKS TO NON-SUN SITES The JDC Tech Tips may provide, or third parties may provide, links to other Internet sites or resources. Because Sun has no control over such sites and resources, You acknowledge and agree that Sun is not responsible for the availability of such external sites or resources, and does not endorse and is not responsible or liable for any Content, advertising, products, or other materials on or available from such sites or resources. Sun will not be responsible or liable, directly or indirectly, for any damage or loss caused or alleged to be caused by or in connection with use of or reliance on any such Content, goods or services available on or through any such site or resource. This issue of the JDC Tech Tips is written by Glen McCluskey. JDC Tech Tips July 12, 2001 Sun, Sun Microsystems, Java, and Java Developer Connection are trademarks or registered trademarks of Sun Microsystems, Inc. in the United States and other countries. To use our one-click unsubscribe facility, select the following URL: http://hermes.java.sun.com/unsubscribe?7422136600014714713