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?-2375002393534047909
==================================
¢º¢¸
//'''' ||'''||
q¢¾.¢¾p q¢¯.£¿p
( v ) ( v )
___oOo___oOo___oOo____oOo__
¢Ï : 019-548-1115
e-mail:bceum@yahoo.co.kr
==================================
|