This "x-ray" tutorial is based on the standard NetBeans tutorials for new users. They are an excellent example of the kinds of operations that end-users normally work with. In italics is the regular tutorial; as commentary on each section, there is a description of what APIs are really being used and how. The original tutorial is a little out of date now, but generally only in superficial ways.
These descriptions are not intended to be 100% complete in the way that a Java-level trace would be. Rather, they should give you the basic idea of how the APIs are being used in context. If you need all the details, then of course a Java debugger or profiling/tracing tool would be more appropriate. Similarly, the real implementation code sometimes uses a slightly different approach than is described here, whether for efficiency or for special UI purposes; however, as a rule the described calls are functionally equivalent.
For this tutorial, we will start with a simple clock form, making use of the built in TimerBean. Once we have this clock compiled and running successfully, we will then extend its functionality, adding the ability to set the time, and change the time and date format.
Let's start with the basic clock.
Startup :
NewTemplateAction
is a
SystemAction
which is always
enabled
(when installed on the Main Window).
It is the general method of creating new objects from template. The
first thing it does is pop up a
tree view
of the Templates
subdirectory of the
system file system,
which may be obtained easily via
Places.Folders.templates()
.
It specially looks for
DataObject
s
which are
templates.
Step back - where did these "templates" come from? Well, modules can
add new templates when they are
installed.
On disk or in XML layers, they are just meaningless files as far as Java is
concerned. But once loaded into the system file system, they are
(hopefully) recognized by a
DataLoader
installed into the system's
DataLoaderPool
by some module or another using a
manifest tag.
In this case, the JFrame.java template file is installed
by the Form Editor's XML layer, and has an
associated JFrame.form file, so it is recognized by the
Form Editor's loader as a form. It is also marked as a template using
file attributes.
When the correct (Form) DataObject
is created, it
is given a type of
DataNode
to represent it to the user. This node provides an
icon
and
display name
to make it more presentable.
DataFolder
on
some file system)
and a name may be chosen (or
defaulted
by the data object). The selector is invoked on the Repository,
filtered
to only display directories.
The data object's
handleCreateFromTemplate(...)
method is called to actually do the creation. In this case, the
templated form's data object is a kind of
MultiDataObject
;
so, the JFrame.java
file within it is in turn asked to
create itself
from template. The implementation of Java source files in the Java
Sources module specifies that their file entries should in fact be
FileEntry.Format
s,
so that "magic" keywords such as __PACKAGE__
will
automatically be substituted in the result. The regular behavior of
file entries during creation from template is of course just to
copy the file in the file system; formatted files do much the same
thing, after substitution, and use calls such as
FileObject.createData(...)
and
FileObject.getOutputStream(...)
to actually copy the data.
InstantiateAction.instantiateTemplate(...)
.
First, the data object's
DataObject.getNodeDelegate()
is found (i.e. created). This is checked for a
customizer component,
which in this case will not exist (none was specified). The last
check is to see if the node has a
default action
associated with it. The Java data object class, when creating its
node delegate, explicitly asks to make
OpenAction
its "default action", so that (e.g.) double-clicking on the node in
an Explorer window will open the source file in the Editor. The
action just looks for the node's
OpenCookie
,
which is provided by
DataEditorSupport
or similar classes and handles the
details of constructing the Editor window, loading the document,
and many other things.
(TopManager.setStatusText(...)
can handle the message - this is done automatically by
DataEditorSupport
in
messageOpening()
.)
It is up to the data object to specify how it will be opened;
the form data object takes the usual approach of using
DataEditorSupport
or a similar support
to handle the details of opening an Editor window with the proper
content (just as a regular Java source file would), but also opens
its own special
TopComponent
s:
the Form Editor and the Component Inspector.
The "guarded" sections of the Java source (by default, with a
light blue background) are handled by special code in the Java
Source module (which the Form Editor module inherits). This code
actually creates a specialized editor support which
looks for special markings in the source file (such as
//GEN-BEGIN:initComponents
) indicating where the
protected areas are.
CloneableEditorSupport.loadFromStreamToKit(...)
is used to detect these, remove them from the actual text, and then
actually guard the areas in the Editor using
NbDocument.markGuarded(...)
.
The Component Inspector lists all current Form Components and their properties. Initially
there are no components except for the default layout (BorderLayout) and a heading for
Non-Visible components - currently empty.
The details of how the component structure of a form is stored
on disk in *.form
files is beyond the scope of the
APIs - this is determined by the Form Editor module. However, it
uses the APIs to then make this component structure accessible to
the user via
nodes.
Remember that the Form Editor defines the data objects for Java
forms, which include some code to specify what sort of node should
represent
them. A regular Java source file (as determined by the Java Source
module) has a node containing the structure of its source-code
elements
within it, e.g. the top-level class, its methods, etc. - these
subnodes are generally created using
SourceChildren
.
A form node then adds an extra subhierarchy corresponding to the
AWT component structure of the form - all of these subnodes are
specially created by the Form Editor and directly mirror its
internal concept of the form as derived from the
*.form
file (including
properties
corresponding to Bean properties of components, and
"meta-properties" like variable name, layout settings, and event
handlers).
So, the Component Inspector is almost "for free" after that - it is just a regular Explorer window plus property sheet that is rooted on the proper "JFrame" node provided by the module. It exists for UI purposes, but does not add functionality beyond the regular Explorer.
Add a component :
These nodes in the regular Explorer also allow the user to
create new folders (Palette tabs), and paste in JavaBeans from the
Repository (using
Node.getPasteTypes(...)
);
and the Install New JavaBean tool scans a JAR file for declared
JavaBeans, then just pastes them into the right area of the Palette
nodes, and
mounts
the JAR as a
new file system.
TopComponent.Registry.PROP_ACTIVATED_NODES
(or related methods in the Window System API) is used - this only
works in a TopComponent
, not in the Main Window.
ExplorerManager.setSelectedNodes(...)
on the manager
associated
with that Inspector.
How is the code added to the Editor? The Form Editor already
"knows" which editor support it is using, though it
could easily find it anyway by
looking
for the
EditorCookie
.
Now, the actual Swing Text API document being used for the source
code is available via
EditorCookie.openDocument()
,
and generic Swing calls suffice to modify the text in it. To add or
remove guard blocks, or otherwise handle text in guard blocks, the
Form Editor may directly call
NbDocument.markGuarded(...)
and so on, though it would typically want to also keep track of
these blocks explicitly from the editor support so it
can name them and find the right one directly.
The Java Hierarchy API could also be used to perform these modifications, but that would have the disadvantage that if the user was actively editing "inert" source code elsewhere in the class while using the Form Editor, the class might not be parseable (i.e. would not be syntactically valid Java source), making it impossible to update with this API.
Modify the component's properties :
BeanInfo
supplied with it probably does not specify a
property editor, so the Java
PropertyEditorManager
looks for a reasonable editor in the search path, and finds that the IDE
ships with
a property editor for java.awt.Font
, which is
used. When the new value is set, the Form Editor
notices
the change and regenerates some code in the Editor window according
to the new
initialization code.
int
, so clearly
using the default property editor would be a poor choice - since
Swing does not do a better job, the IDE specially provides a
property editor for such properties using a list of named
selections.
Functionality - Adding Code :
import java.util.Date; import java.util.GregorianCalendar; import java.util.Calendar; import java.text.SimpleDateFormat;
We will also use the standard JOptionPane for error messages :
import javax.swing.JOptionPane;
private GregorianCalendar gCal = new GregorianCalendar(); private String timeFormat = "hh:mm:ss"; private SimpleDateFormat formatter = new SimpleDateFormat(timeFormat);User edits of this sort leave the APIs inactive - they entirely fall within the scope of the Editor in use, and its
StyledDocument
.
Add the TimerBean, and set an event handler :
gCal.add(Calendar.SECOND,1); String timeTxt = formatter.format(gCal.getTime()); if (jlblCurrentTime != null) jlblCurrentTime.setText(timeTxt);
Compiling and Executing the form :
ExecuteAction
is a
CookieAction
,
meaning that it automatically enables or disabled itself (and its
toolbar
presentation)
depending on whether the selected item is really supposed to be
executable.
TopComponent.Registry.PROP_ACTIVATED_NODES
informs the action which node(s) is currently "selected" in some
TopComponent
,
usually either an Explorer or Editor window (which is why you can
execute when you have the source file opened, or when you click on
its node to select it). The CookieAction
implementation then
checks
on that node to see whether it supports the desired cookie - in this
case,
ExecCookie
.
Now, in the default IDE configuration, files should be compiled
before execution to make sure the test run is accurate. So, the
ExecuteAction
checks for this flag in the Execution
SystemOption
,
and finding "Run Compilation" turned on, first tries to compile the
JFrame
source. This step is somewhat complex, as it
requires querying the file and perhaps the system to see how source
code should be compiled, then setting up a compiler job to do so
and running it - so you should refer to the
Compiler API
for all the details.
Starting the execution is pretty trivial - the action simply
takes the cookie it found and calls
ExecCookie.start()
.
But who specifies how to do this? The Java Sources module adds an
ExecCookie
to data objects it creates (if they have
the proper main
function, say). This cookie is
implemented by
ExecSupport
,
which permits the user to choose from among standard
Executor
s
installed in the system - it may be used to
add node properties
to the data object's node representing the current choice (there is
a standard property editor which lists the choices in a
pulldown). The user can also set command arguments, etc.
The IDE automatically installs two executors into the pool,
since they are available in the APIs (not in modules):
ProcessExecutor
and
ThreadExecutor
(corresponding to external and internal execution, resp.). The user
of this tutorial did not specify which to use, so one is already
selected as the default, probably ProcessExecutor
. Now
the ExecSupport
when asked to run the frame, calls
Executor.execute(ExecInfo)
;
after some shuffling of flow of control through various parts of
the API implementation (to make sure the process is correctly
handled, I/O is redirected, and so forth),
ProcessExecutor.createProcess(ExecInfo)
is called which uses Runtime.exec(...)
(via
NbProcessDescriptor.exec(Format)
)
to create the
process and run it.
Workspace.activate()
;
in this case, the ExecuteAction
looks in the Execution
Settings for the name of the right workspace to switch to. The
Execution View is a core IDE feature - it is an Explorer window
opened on a special Environment node displaying all processes run
using the Execution API.
That's it!
Again assuming there are no errors, your clock should be displayed, showing the correct current time, with the seconds incrementing normally.
You've just built your first form !
To close the form, right-click on it in the Execution View, and select Terminate Process.
Note that while you can also terminate this form by closing the form window, this relies on
the WindowClosing event being set. The JFrame Template we used to build this form
has this event set to close the application as a default setting. Without it,
closing the window would not actually terminate the process.
The Execution View nodes have an action in their
popup menu
permitting the associated process to be terminated using
ExecutorTask.stop()
.
In this tutorial we will demonstrate the use of the debugging subsystem of the IDE. We will use the completed code for one of the earlier tutorials - part three of Tutorial One, the advanced version of the Clock. The completed code for this tutorial (and all other tutorials) is included with NetBeans, and can be found under Development/tutorial/ in the NetBeans Explorer.
The Debugger allows you to set and remove breakpoints, watch variables, track the state of threads, and more. All of this can be done in an easy and intuitive graphical user interface.
Preliminary Setup :
Working with Breakpoints
CompileAction
works essentially the same way as the automatic compile discussed
above in the context of execution.
EditorSupport
,
notices
when its Editor window is active, and uses
CallbackSystemAction.setActionPerformer(...)
to make the
ToggleBreakpointAction
do the right thing. It must find where the user currently has the
cursor positioned in the document (as usual, the Swing
Document
interface is used for this), then both
adds
a breakpoint to the current debugger implementation, and
marks
that line in the document internally as having a breakpoint (which
in turn actually
colors
the line in the Editor window).
The Output window is split vertically, the left hand panel displaying the
output of the debugged program, the right panel showing messages from the
debugger itself.
The
GoAction
does not do much itself. Basically, it makes sure the selected node
is debuggable by looking for a
DebuggerCookie
;
spawns a new thread (since the debugging should be started as soon
as possible, but may take a while to "warm up"); automatically
compiles the file in the same way as mentioned above for execution;
and finally hands off the real work to
DebuggerCookie.debug(...)
.
Normally this cookie will be implemented by an
ExecSupport
(a very useful cookie support to use!), which gets the
correct
DebuggerType
describing how to launch the process in the debugger, which in turn
gets the
system debugger,
then goes ahead and
starts it.
It is up to the installed Debugger module to determine exactly
what to do during debugging, provided it implements the basic
Debugger
interface
to permit itself to be controlled in a few ways by other components
of the system. In this case, it is using the Execution API to run
the external JDK debugger (and
display
results in the Output Window); switching to the Debugging workspace
(as described for execution); and opening a custom Explorer view in
the form of the three-tabbed Debugger Window (actually displaying
the contents of a special Environment node created by the module).
The Debugger window is used to manipulate breakpoints, watch program
variables, and the state of threads. These are each displayed under a
seperate tab. Currently under the Breakpoints tab you will see the
breakpoint we have just set, listed by source name and line number.
Breakpoints and watches are specified in general terms by the
Debugger API;
however the debugger module is providing nodes to represent them
visually to the user (and present an alternative way of organizing
and editing them). These nodes may provide whatever structure the
debugger implementation needs, although since breakpoints and
watches are actually configurable as JavaBeans, using
BeanNode
would actually suffice for most of the display. Threads and thread
groups are not specified by the API, so the debugger has its own
representation of these which it displays in specific ways (with
children for the call stack, locales, and so on).
Repository.find(...)
- and
then
gets the line
where the breakpoint was set (this cannot be done just by jumping
to the desired line in the Editor, since the user may have added or
removed lines of text since the last compilation!), and uses
Line.markCurrentLine()
to indicate that the debugger is stopped at that line (which is
also visually displayed with
NbDocument.markCurrent(...)
).
At this point you can continue (F5), Trace Over the current line (F8), or Trace into the function called on the current line (F7). We wish to step into ClockFrame, so push F7, or select Trace Into from the Debug menu on the Main Window.
You will next break at the first of the three variable declarations
manually entered when creating the tutorial (private GregorianCalendar
gCal ...). Push F8 to trace over this; Trace over both others by
pushing F8 twice more.
All of these actions simply invoke the proper method on the
Debugger
interface, the implementation of which controls the JDK debugger
agent (in this case).
The pink line highlighting the current point in the code should now be
at the line reading "initComponents ();". Push F7 to
trace into this. Hit F8 twice until you are halted on the line where
the instance of org.netbeans.examples.lib.timerbean.Timer is created, press F7 to
step in. Assuming you have mounted the sources directory as a
Filesystem as described in the Preliminary Setup section, the Timer
source will open in the Editor window, and the Debugger session now
steps into it, the pink highlighted line indicates the point in the
source where the Debugger is stopped.
Note that when you trace into files that are not yet opened,
but that exist in source form on some filesystem, they will be
opened for you using
EditorCookie.openDocument()
.
Watching Variables
As you use F5, F7 and F8 to continue, step into and step over the code respectively,
you can monitor the values of the watched variables at each stage.
Note that, as suggested above, much of the interesting behavior
of the breakpoints and watches can be fully captured with a
completely generic BeanNode
, if nothing more complex
is provided: here, the current value of a watch is accessible to
the system (as well as the node representative) via
Watch.getAsText()
,
and standard Java property changes are fired on
Watch.PROP_AS_TEXT
whenever this text changes.
Threads
Other Features
Breakpoint
and
Watch
are serializable, and the set of them is stored using serialization
while the IDE is shut down (the IDE core handles this
automatically); it is even possible that a single set of
breakpoints and watches be reused between two different debugger
implementations (modules), if no special customizations are made to
these objects.
Debugger.finishDebugger()
and the debugger implementation handles the details of stopping the
process, cleaning up the "current line" marks in the Editor window,
and so on.