Use NetBeans to Create HelloXlet
3.8 Profiling and Debugging with NetBeans
Part II Java Virtual Machine Reference
4. Java Virtual Machine Capabilities
8. External PBP Porting Layer Plugin
This chapter describes the basic application object and its life cycle. It also describes how to write, build and deploy simple applications.
Developing for Oracle Java Micro Edition Embedded Client is similar to developing Java Platform, Standard Edition (Java SE) applications. Experience with AWT on the desktop helps in learning to write Oracle Java ME Embedded Client applications.
Note - Please ensure that you have the specific hardware and software environment described in the Oracle Java Micro Edition Embedded Client Installation Guide.
The Oracle Java ME Embedded Client Emulator allows application developers to develop, build and test Java ME Embedded client applications in an integrated IDE environment such as NetBeans. Developers can work with command line tools directly, rather than using an IDE interface.
The emulator runs on Windows or Linux/x86 systems. It is available when you use the OJEC platform in NetBeans or Eclipse, or it can be launched from the command line. For an overview of command line options, see the Oracle Java Micro Edition Embedded Client Installation Guide.
NetBeans offers full integration with the SDK. You can build applications, run them on the emulator, and use the profiling and debugging features to test your application.
Eclipse integration is limited to application development and emulation.
The Oracle Java Micro Edition Embedded Client Installation Guide describes how to configure these IDEs to work with the SDK.
The API documentation for JSRs supported in this release can be found in the SDK installation at /usr/local/Oracle_JavaME_Embedded_Client/1.0/docs or C:\Program Files\Oracle\Oracle JavaME Embedded Client\1.0\docs.
CDC, FP, and PBP are found online here:
http://download.oracle.com/javame/embedded.html
If you prefer to download the optional package documentation and install it locally, consult the Java Community Process (JCP) program web site:
http://jcp.org/en/jsr/detail?id=218
The Oracle Java ME Embedded Client supports two application life cycle models. The basic model is the traditional main() application model and the more TV-centric model is an Xlet (javax.microedition.xlet.Xlet).
The traditional application model is quite simple: load a class, invoke its main() method, and wait until all non-background threads terminate or System.exit() is called. In version 1.0 the available target is headless, so applications for the target must use this model. You can see examples of this model in the Oracle Java Micro Edition Embedded Client Installation Guide.
For many applications, this model allows too little control over the application's behavior. Personal Basis Profile defines its own application model, similar in many ways to the MIDlet model. The Xlet model has been borrowed from the Java TV API, where it is used to control application life cycles in set-top boxes. The model's two key elements are the Xlet and XletContext interfaces, both found in the javax.microedition.xlet package. The application's main class implements the Xlet interface, which defines event methods for the system to invoke. The XletContext interface defines callback methods through which an application can obtain information about its operating environment.
As most Java platform programmers are familiar with the main application model, this section briefly describes the Xlet life cycle.
An Xlet implements the javax.microedition.xlet.Xlet interface, which declares four life-cycle notification methods: initXlet(), startXlet(), pauseXlet(), and destroyXlet(). Note that, unlike applets or MIDlets, an Xlet does not have to extend any particular class.
Like applets, Xlets have four possible states:
loaded: The Xlet instance has been constructed, but no initialization has yet occurred.
paused: The Xlet has been initialized but is inactive.
active: The Xlet is active.
destroyed: The Xlet has been terminated and is ready for garbage collection.
When the Application Management System (AMS) creates an Xlet, it starts in the loaded state. Soon after construction, the AMS invokes the Xlet's initXlet() method, passing the XletContext that the Xlet must use to interact with its operating context (this parameter is necessary because the Xlet does not extend a specific base class). After initialization, the Xlet changes to the paused state.
At this point, an Xlet behaves more like a MIDlet than an applet. At some point, the AMS activates the Xlet and invokes its startXlet() method. The Xlet activates its user interface, obtains the system resources it needs to function properly, then shifts to the active state.
Deactivation occurs when the Xlet's state changes from active to paused. When the AMS deactivates the Xlet, it invokes the Xlet's pauseXlet() method. The Xlet frees as many resources as possible. If the Xlet deactivates itself, however, the pauseXlet() method is not invoked.
An Xlet can change to the destroyed state at any time. If the AMS destroys the Xlet, it invokes the destroyXlet() method. Like MIDlets, Xlets can sometimes abort their destruction by throwing an exception. If an Xlet destroys itself, destroyXlet() is not invoked.
With the Oracle Java ME Embedded Client Emulator installed, you can write, build, and execute applications using the PBP 1.1 API.
In an application, the device decides what portion of the screen it wants to give to your application.
Note - If you use the SDK on Windows the emulator will mimic the device’s screen size behavior. On Linux, although the SDK does not adjust the window size. In both cases, the window size is rendered correctly on the device.
This is supplied as an AWT Container. You can get at it from the XletContext that the device passes to your application’s initXlet() method.
You now know enough about Xlets to create your first application. Below is the source code for HelloXlet.java, which simply displays a message on the screen and exits when you press any key.
package helloxlet; public class HelloXlet implements javax.microedition.xlet.Xlet { /** * Default constructor without arguments should be. */ public HelloXlet() { } /** * Put your initialization here, not in constructor. * If something goes wrong, XletStateChangeException should be thrown. */ public void initXlet(javax.microedition.xlet.XletContext context) throws javax.microedition.xlet.XletStateChangeException { // TODO implement initialization } /** * Xlet will be started here. * If something goes wrong, XletStateChangeException should be thrown. */ public void startXlet() throws javax.microedition.xlet.XletStateChangeException { // TODO implement } /** * Free resources, stop unnecessary threads, remove itself from the screen. */ public void pauseXlet() { // TODO implement } /** * Destroy your xlet here. If parameter is false, you can try to not * destroy xlet by throwing an XletStateChangeException */ public void destroyXlet(boolean unconditional) throws javax.microedition.xlet.XletStateChangeException { // TODO implement } }
Most of the excitement happens in the initXlet() method, which retrieves the Xlet’s visual space and saves it in the member variable context. Then an inner key listener is registered to exit the application when a key is pressed.
When the Xlet is started or paused, context is shown or hidden as appropriate. The call to requestFocus() ensures that key events are delivered to the Xlet’s container, which is where the listener is registered.
This Xlet uses a very simple Component subclass to supply its visual content. The default Main template provides sample code that will work with HelloXlet.java.
package helloxlet; import javax.microedition.xlet.*; import java.awt.BorderLayout; import java.awt.Component; import java.awt.Container; import java.awt.Color; import java.awt.Dimension; import java.awt.Graphics; import java.awt.Font; // Create the Main class. public class Main extends Component implements Xlet { private Container rootContainer; private Font font; // Initialize the xlet. public void initXlet(XletContext context) { log("initXlet called"); // Setup the default container // This is similar to standard JDK programming, // except you need to get the container first. // XletContext.getContainer gets the parent // container for the Xlet to put its AWT components in. // and location is arbitrary, so needs to be set. // Calling setVisible(true) make the container visible. try { rootContainer = context.getContainer(); rootContainer.setSize(400, 300); rootContainer.setLayout(new BorderLayout()); rootContainer.setLocation(0, 0); rootContainer.add("North", this); rootContainer.validate(); font = new Font("SansSerif", Font.BOLD, 20); } catch (Exception e) { e.printStackTrace(); } } // Start the xlet. public void startXlet() { log("startXlet called"); //make the container visible rootContainer.setVisible(true); } // Pause the xlet public void pauseXlet() { log("pauseXlet called"); //make the container invisible rootContainer.setVisible(false); } // Destroy the xlet public void destroyXlet(boolean unconditional) { log("destroyXlet called"); //some cleanup for the xlet.. rootContainer.remove(this); } void log(String s) { System.out.println("SimpleXlet: " + s); } public void paint(Graphics g) { int w = getSize().width; int h = getSize().height; g.setColor(Color.blue); g.fill3DRect(0, 0, w - 1, h - 1, true); g.setColor(Color.white); g.setFont(font); g.drawString("Hello Java World", 20, 150); } public Dimension getMinimumSize() { return new Dimension(400, 300); } public Dimension getPreferredSize() { return getMinimumSize(); } }
Follow these steps to create a Netbeans project from two source files. The steps are very similar for Eclipse.
The project wizard opens.
The Main file is created automatically. Main uses the container in the HelloXlet.java file discussed earlier. The application prints the message “Hello Java World” in white type in a window with a blue background.
Note, on Windows the size of the window in the emulator is determined by the device selection. On Linux the emulator cannot set the window size, but it will be displayed properly on the device.
Oracle Java ME Embedded Client does not include a user interface (UI) toolkit. You don’t get any buttons, lists, combo boxes, or any other user interface components. This is both challenging and liberating.
You’re not left out in the cold entirely. Oracle Java ME Embedded Client does include most of the infrastructure you need to build a UI toolkit, just not the actual components (widgets) themselves.
If you’ve done any work with AWT or Swing on the desktop, you’ll feel right at home. In essence, Oracle Java ME Embedded Client provides AWT without the usual components.
The fundamental structure is defined by java.awt.Component and java.awt.Container. A Component is something that shows up on the screen, like a button, text field, or movie. A Container is simply a visual group of Components. Container has a very useful concept, the layout manager which is an object that places the Components of a Container in a certain way. Oracle Java Platform ME Embedded Client includes several useful layout managers.
Most user interfaces are composed of a variety of Components, Containers, and LayoutManagers. This kind of composition is possible because a Container can hold other Containers.
The XletContext passed to your application’s initXlet() method has a reference to a Container. Your application builds its entire user interface on this Container.
You can use someone else’s UI toolkit, or you can create your own.
This section describes how to create a simple UI button from scratch using Xlets and containers. The first step is to create a subclass of Component, which provides lots of useful plumbing.
Your subclass of Component has to provide implementations for a few methods to appear on the screen:
Your implementations of getPreferredSize(), getMinimumSize(), and getMaximumSize() help the device in laying out your component in its parent container.
The body of your component’s paint() method determines how the component shows itself on the screen.
Below is a simple class, DTVButton, which shows a rudimentary round-cornered rectangular button with a text label. The button exists in one of three states, either normal, focussed, or pressed, which determine the colors that are used to display the button.
DTVButton uses its text label to calculate how big it wants to be. It returns the same size for the preferred, minimum, and maximum sizes.
package dtvui; import java.awt.*; public class DTVButton extends Component { private static final int kPad = 6; public static final int kNormal = 0; public static final int kFocus = 1; public static final int kPressed = 2; private static Color sBG = Color.darkGray; private static Color sFG = Color.gray; private static Color sBGH = Color.pink.darker(); private static Color sFGH = Color.red.darker(); private static Color sBGC = Color.pink; private static Color sFGC = Color.red; private String mLabel; private Dimension mPreferredSize; private int mState; public DTVButton(String label) { setLabel(label); mState = kNormal; } public void setLabel(String label) { mLabel = label; } public String getLabel() { return mLabel; } private void calculate() { Graphics g = getGraphics(); FontMetrics fm = g.getFontMetrics(); int tw = fm.stringWidth(mLabel) + kPad * 2; int th = fm.getHeight() + kPad * 2; mPreferredSize = new Dimension(tw, th); } public void setState(int state) { boolean dirty = (mState != state); mState = state; if (dirty) repaint(); } public int getState() { return mState; } public void paint(Graphics g) { int w = getWidth(); int h = getHeight(); Color bg, fg; switch(getState()) { case kFocus: bg = sBGH; fg = sFGH; break; case kPressed: bg = sBGC; fg = sFGC; break; case kNormal: default: bg = sBG; fg = sFG; break; } g.setColor(bg); g.fillRoundRect(0, 0, w, h, kPad, kPad); g.setColor(fg); g.drawRoundRect(0, 0, w - 1, h - 1, kPad, kPad); FontMetrics fm = g.getFontMetrics(); int tw = (int)mPreferredSize.width; int th = (int)mPreferredSize.height; g.drawString(mLabel, (w - tw) / 2 + kPad, (h - 1 - th) / 2 + fm.getAscent() + kPad); } public Dimension getPreferredSize() { if (mPreferredSize == null) calculate(); return mPreferredSize; } public Dimension getMinimumSize() { return getPreferredSize(); } public Dimension getMaximumSize() { return getPreferredSize(); } }
Now you can create one or more DTVButtons and show them on the screen, but they can’t do anything unless you add some event handling.
The DTVButtonGroup class, below, serves to group buttons and handles key events. It enables the user to navigate through a list of buttons using the arrow keys. A button can be pressed with the Enter or select key. In this case, an event is fired to a listener object.
package dtvui; import java.awt.event.*; import java.util.*; public class DTVButtonGroup implements KeyListener { private int mFocus; private List mButtonList; private DTVButtonListener mListener; public DTVButtonGroup() { mFocus = 0; mButtonList = new ArrayList(); } public void add(DTVButton button) { mButtonList.add(button); if (mButtonList.size() == 1) button.setState(DTVButton.kFocus); } public void setDTVButtonListener(DTVButtonListener listener) { mListener = listener; } // KeyListener methods. public void keyPressed(KeyEvent ke) { int newState = DTVButton.kFocus; DTVButton old = (DTVButton)mButtonList.get(mFocus); old.setState(DTVButton.kNormal); int code = ke.getKeyCode(); if (code == KeyEvent.VK_UP || code == KeyEvent.VK_LEFT) { mFocus--; if (mFocus < 0) mFocus = mButtonList.size() - 1; } else if (code == KeyEvent.VK_DOWN || code == KeyEvent.VK_RIGHT) { mFocus++; if (mFocus >= mButtonList.size()) mFocus = 0; } else if (ke.getKeyCode() == KeyEvent.VK_ENTER) { newState = DTVButton.kPressed; } DTVButton b = (DTVButton)mButtonList.get(mFocus); b.setState(newState); } public void keyReleased(KeyEvent ke) { int code = ke.getKeyCode(); if (code == KeyEvent.VK_ENTER) { DTVButton b = (DTVButton)mButtonList.get(mFocus); b.setState(DTVButton.kFocus); if (mListener != null) mListener.pressed(b); } } public void keyTyped(KeyEvent ke) {} }
Typical usage of these classes is to create some DTVButtons, add them to a group and add them to a visual Container, and register a listener for the group. The listener interface is very simple:
package dtvui; public interface DTVButtonListener { public void pressed(DTVButton button); }
The Main.java file sets up the containers and manages the Xlet status. This is similar to JDK programming, except you must get the container first. context.getContainer gets the parent container where the Xlet puts its AWT components. The size and location is arbitrary, so the values must be set. Calling setVisible(true) makes the container visible.
package dtvui; import javax.microedition.xlet.*; import java.awt.BorderLayout; import java.awt.Component; import java.awt.Container; import java.awt.Color; import java.awt.Dimension; import java.awt.Graphics; import java.awt.Font; // Create the Main class. public class Main extends Component implements Xlet, DTVButtonListener{ private Container rootContainer; private Font font; private DTVButtonGroup group; private DTVButton b1, b2, b3; // Initialize the xlet. public void initXlet(XletContext context) { log("initXlet called"); // Setup the default container try { rootContainer = context.getContainer(); rootContainer.setSize(600, 600); rootContainer.setLayout(new BorderLayout()); rootContainer.setLocation(0, 0); rootContainer.add("North", this); font = new Font("SansSerif", Font.BOLD, 20); group = new DTVButtonGroup(); rootContainer.addKeyListener(group); b1 = new DTVButton("Uno"); group.add(b1); b1.setVisible(true); rootContainer.add("West", b1); b2 = new DTVButton("Due"); b2.setVisible(true); group.add(b2); rootContainer.add("South", b2); b3 = new DTVButton("Tre"); b3.setVisible(true); group.add(b3); rootContainer.add("East", b3); group.setDTVButtonListener(this); rootContainer.validate(); } catch (Exception e) { e.printStackTrace(); } } // Start the xlet. public void startXlet() { log("startXlet called"); //make the container visible rootContainer.setVisible(true); rootContainer.update(rootContainer.getGraphics()); b1.setVisible(true); b2.setVisible(true); b3.setVisible(true); } // Pause the xlet public void pauseXlet() { log("pauseXlet called"); //make the container invisible rootContainer.setVisible(false); } // Destroy the xlet public void destroyXlet(boolean unconditional) { log("destroyXlet called"); //some cleanup for the xlet rootContainer.remove(this); } void log(String s) { System.out.println("SimpleXlet: " + s); } public void paint(Graphics g) { log("Main Paint"); int w = getSize().width; int h = getSize().height; g.setColor(Color.blue); g.fill3DRect(0, 0, w - 1, h - 1, true); g.setColor(Color.white); g.setFont(font); g.drawString("Button Layout Example", 20, 150); } public Dimension getMinimumSize() { return new Dimension(400, 300); } public Dimension getPreferredSize() { return getMinimumSize(); } public void pressed(DTVButton button) { group.keyPressed(null); } }
Examine the source code to see how you can respond to button presses to make interactive applications.
To support debugging and profiling for the Oracle Java ME Embedded Client, NetBeans establishes a connection between the IDE and the target device. The target can be an embedded device running the Oracle Java ME Embedded Client, or the emulator available with the Oracle Java ME Embedded Client SDK, which is typically installed on the same machine as the NetBeans IDE. In both debugging and profiling, the CVM sets up a server over a server socket on the target device, and NetBeans connects as a client. In the case of the profiler, the methods to be profiled are rewritten with extra bytecode so that profiling can take place. The VM is started with special flags. The handshaking must take place before you launch the application.
Note - The NetBeans profiling features Take Snapshot and Take Heap Dump are not supported in this release. Also, profiling of ROMized system classes is not supported.
If you are profiling or debugging locally (using the emulator) the installation includes everything required. If you are using a remote target, download and install the ARM runtime and mount it from the target. This process is described in the Oracle Java Micro Edition Embedded Client Installation Guide.
In the SDK the CVM executable is found at:
InstallDir/Oracle_JavaME_Embedded_Client/1.0/emulator-platform/bin/cvm
On the target the distribution offers the JVMTI (JVM tooling interface) and Production binaries. Debugging and profiling requires the JVMTI virtual machine. This is found at:
InstallDir/Oracle_JavaME_Embedded_Client/1.0/binaries/jvmti/bin/cvm
To profile or debug you launch the JVMTI version of the CVM in a special "server" mode on the target. NetBeans attaches to this as a profiling or debugging "client".
Both debugging and profiling work this way, with the server on the target VM and the client running in NetBeans.
To profile you must know the full IP address or host name of the target device.
Note - If are profiling locally (on the SDK installation host) you must still supply an IP address when you are running the SDK and the VM on the host machine. Using Localhost or 127.0.0.1 does not work.
The following assets are used in profiling and must be available on the target device.
The profiler interface library.
This can be in the form of a shared object file (libprofilerinterface.so on Linux or a .dll file on Windows).
The jfluid-server.jar file.
The jfluid-server-cvm.jar file.
Calibration data. Produced when you run the calibration script (see Step 1 in Profiling a Test Application).
This procedure describes profiling an application running on a local host, but the process is basically the same with a remote target. For information on installing the OJEC stack on a client and placing applications on a client, see the Installation Guide.
Before You Begin
To see profiling results your application must create sufficient data. Use your own program, or create an Oracle Java ME Embedded Client project named Test which uses the following Main.java source code:
package test; public class Main { public static void main(String[] args) { int i, j, k; boolean prime; String demoString; i = 98890000; while (true) { prime = true; for (j=2; (j*j) <= i; j++) { k = i / j; if ((k*j)==i) { demoString = new String(String.valueOf(i)); prime = false; } } if (prime) try { System.out.println(i + " is prime"); Thread.sleep(100); demoString = null; } catch (Exception e) { //Ignore } i++; } } }
Follow these steps to launch a profiling session:
In the profiling directory, run the calibration script. You only need to do this once.
Linux:
/usr/local/Oracle_JavaME_Embedded_Client/1.0/emulator-platform/ lib/profiler/calibrate.sh
Linux Target:
installdir/Oracle_JavaME_Embedded_Client/1.0/binaries/jvmti/ lib/profiler/calibrate.sh
Windows XP:
\Program Files\Oracle\Oracle JavaME Embedded Client\1.0\ emulator-platform\lib\profiler\calibrate.bat
On Linux the calibration data is stored in your home directory in a directory named: .nbprofiler. On Windows, the calibration data is stored in: Documents and Settings\User\.nbprofiler.
In this example we chose memory.
These settings will persist. You do not have to repeat the define steps. You can change the profiling type each time you attach the profiler.
This example refers to a project named Test and the commands are issued from the emulator-platform directory.
Linux:
./bin/cvm -Xms32m -agentpath:./lib/libprofilerinterface.so=./lib/profiler/lib,5140 -Xbootclasspath/p:./lib/profiler/lib/jfluid-server-cvm.jar -cp path-to-NetBeansProjects/Test/dist/Test.jar test.Main
Linux Target:
On the target the directory structure is slightly different, as described in 3.8.1 Profiling and Debugging VMs. The call is much the same but you use the VM found in: /Oracle_JavaME_Embedded_Client/binaries/jvmti/bin.
Windows:
bin\emulator.exe -Xms32m -agentpath:bin\profilerinterface.dll=lib\profiler\lib,5140 -Xbootclasspath/p:lib\profiler\lib\jfluid-server-cvm.jar -cp path-to-NetBeansProjects\Test\dist\Test.jar test.Main
The CVM launches the application and waits for the profiler to connect. You see:
Profiler Agent: Initializing Profiler Agent: Options: >./lib/profiler/lib,5140 Profiler Agent: Initialized successfully 15.28 Profiler Agent: Waiting for connection on port 5140
Select Profile > Attach Profiler. You can change the type of profiling (Monitor, CPU, or Memory) and set type-specific options before selecting Attach.
To see the results, select Window > Profiling and choose the views you want displayed. For example, Telemetry Overview. These options are fully described in the NetBeans online help.
The Save Current View to Image feature takes a graphic snapshot of the current view :
If you are finished looking at the results and the application is still running, stop it from the command line with CTRL-C.
Before You Begin
You can debug applications on the SDK host using standard NetBeans tools. Although it is not the same as debugging on the target, local debugging is worth doing because it is simple and it’s useful for fixing generic problems unrelated to device functionality.
Note - Only interpreted code can be debugged.
The application runs, stopping at any breakpoints. You can continue or step through the code. See the NetBeans help for details on the options in the Debug menu. To end the session, choose Debug > Finish Debugger Session.
To debug an application running on a target device you must configure a connection between the NetBeans IDE and the device. When the application is launched you must invoke an agent that can communicate the JDWP protocol so that debugging can take place.
Before attempting to run the debugger on your remote system, verify that you can run the application without debugging. When your application runs successfully, add the following parameters to the command you are using to launch the application:
-Xdebug -agentlib:jdwp=transport=dt_socket,address=53955,server=y,suspend=y
Note: the agentlib:jdwp arguments are separated with commas; do not insert spaces in the jdwp argument string.
InstallDir/Oracle_JavaME_Embedded_Client/binaries/jvmti/bin/cvm -Xdebug -agentlib:jdwp=transport=dt_socket,address=53955,server=y,suspend=y -cp YourProjectDir/Test/build/compiled test.Main
When the connection is formed you see the following message in the console:
Listening for transport dt_socket at address: 53955
This opens the Attach dialog window. The settings should be similar to those shown below.
The host can be an IP address or a server name, for example, server.sfbay.sun.com.
The port value 53955 is the default. You can change the port value as long as it matches the value of the address argument you specified in Step 2.
Adjust the Timeout value as you see fit.
Click OK. The application loads and runs until it reaches a breakpoint.
The output appears in a Debugger Console tab in the Output window.
Consult the NetBeans help for descriptions of the NetBeans debugging options.