Return-Path: <env_7422136600014714713@hermes.java.sun.com>
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 <alexp@mit.edu>; 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" <body_7422136600014714713@hermes.java.sun.com>
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
