If these things are straightforward to do, why isn't there just a utility method to do them? In these cases the desired functionality is really a multi-stage process that the APIs require you to logically break up into separate pieces, each of which the APIs can handle cleanly. That way you have more control over the details.
FileObject
? How do I convert them?
A: Raw files on disk are generally represented in Java using
java.io.File
.
These correspond directly to what the operating system thinks of as
a file.
In the Open APIs, raw files are very rarely manipulated
directly. Rather, you should almost always be using
FileObject
s.
Besides the fact that virtually everything else in the APIs that
works with files expects FileObject
s, these have a
number of advantages:
File
s, unless you need to interface to external
tools. However, in case translation from one to the other is really
needed:
FileObject
representing a
File
, search through the
Repository's file systems
for
LocalFileSystem
s
(or any with one class path entry from
FileSystem.Environment
,
that being the path to a directory), and see if any such file
system has a root directory which is a prefix of the file. If so,
strip off the prefix and look up the right FileObject
.
If there is no matching directory, you will need to create a
LocalFileSystem
(e.g.) to hold the file object; you
will need to
mount
this file system in the Repository if you expect compilation,
execution, or debugging to work. (However, you may use
setHidden (true)
to reduce the UI impact of doing so.) Choose the root
directory for the new file system carefully; i.e. if you are
trying to represent a Java source file, check for a
package
statement and mount the proper root directory
based on this.
The source code for the Open File utility module demonstrates these techniques.
File
from a
FileObject
, you may use
NbClassPath.toFile(...)
.
Always remember to check for null
as a return value and
handle this gracefully!
NodeAction
).
I think it corresponds to a file in the Repository; how can I get
that file?
A: Just get the DataObject
as a cookie and go from
there:
Node n = ...; DataObject dob = (DataObject) n.getCookie (DataObject.class); if (dob == null) { // not a file node } else { // could also get all files in the data object, if desired: FileObject fo = dob.getPrimaryFile (); // do something with fo }
*.class
file) it came from?
A: Use
ClassElement.forName(String)
.
This will give you the class element, if one can be found by that
name.
You can try to look for the file it came from, too. However, be
aware that elements do not need to come from any physical source:
e.g. ClassElement.forName("java.util.Vector")
will
probably give you an element that is not based on any file, but
rather the live Vector
class in the Java VM. With that
caveat:
ClassElement ce = ClassElement.forName (myClassName); if (ce != null) { SourceElement se = ce.getSource (); if (se != null) { DataObject dob = (DataObject) se.getCookie (DataObject.class); if (dob != null) { FileObject fo = dob.getPrimaryFile (); // check if desired that fo.getExt ().equals ("java"), etc. } else { // didn't specify where it came from } } else { // no containing source, probably a class found only in the VM } } else { // not found at all }
A: Yes, you could; but please instead use the standard windowing system to do these sorts of things. Then you will properly handle workspace switching, docking, context help, various keyboard shortcuts, and many other things contributing to the IDE's appearance and functionality.
The
Window System API
describes the general steps you should use to open up new function
windows, if you need such a thing. Specifically, you should use
TopComponent
s
for such purposes.
You can also use
TopManager.notify(NotifyDescriptor)
to show dialogs that interact well with the IDE's window system,
and have a number of bits of prebuilt UI. You can use various
standard subclasses of NotifyDescriptor
to represent
simple messages; exceptions; general-purpose dialogs with content
panels; or even multi-stage wizards.
A: Yes, you can do all of these things. Refer to the Actions API for details.
A: Yes; you need to first get the selected node (which if the Editor is selected, should correspond to the file being edited); get the most recent editor pane open on it; and then access the caret:
Node[] n = TopComponent.getActiveComponent ().getActivatedNodes (); if (n.length == 1) { EditorCookie ec = (EditorCookie) n[0].getCookie (EditorCookie.class); if (ec != null) { JEditorPane[] panes = ec.getOpenedPanes (); if (panes.length > 0) { int cursor = panes[0].getCaret ().getDot (); String selection = panes[0].getSelectedText (); // use this info somehow... } } }
A: Yes, any place where the APIs expect to have an item installed into a popup or regular menu, you can provide a submenu instead. Usually this is done with a dummy action whose popup and menu presenters is a submenu. See the Actions API for details.
A: This is possible using code such as:
DataLoader loader = DataLoaderPool.firstProducerOf (SomeDataObject.class); if (loader != null) { SystemAction[] actions = loader.getActions (); SystemAction[] newactions = new SystemAction[actions.length + 2]; System.arraycopy (actions, 0, newactions, 0, actions.length); // Really, take more care that it is not a duplicate, // place into a specific position, etc.: newactions[actions.length] = null; newactions[actions.length + 1] = SystemAction.get (SomeAction.class); loader.setActions (newactions); }But you should avoid doing this unless it is really critical to usability. Generally service actions should be used in such situations - then the new action will be in the popup under the submenu Tools..., on most nodes. This is much easier to do and safer.
A: If the file was added to a containing directory or
otherwise modified very soon before you tried to use the
FileSystems API to access it, it is possible the change was not yet
visible to the IDE. This can happen if the change is made by use of
java.io.File
or an external process; file systems
which use
AbstractFileSystem.refreshTime
to implement auto-refreshing caches do not automatically
rescan the disk every time a request is made for information about
file objects. Rather, when the next refresh occurs, or
FileObject.refresh()
is called on the containing folder, the cache is updated (and
events fired to inform other code). If you know that some sort of
external modification of files is possible immediately before you
are doing an access, please use refresh()
explicitly
to make sure the caches are synchronized.
A: Yes and no. Yes, because all NetBeans modules are Java code, which is written as cleanly as possible and often provides fairly well-defined interfaces to its functionality that could be reusable, and which NetBeans programmers do use to communicate between modules in some case. No, because currently none of these interfaces are properly documented.
For example, the Java Sources Module (which provides a loader
for *.java
files, and most of their associated
functionality such as parsing, compile, source editing, etc.) does
have an API, i.e. a set of documented (as Javadoc) conventions as
to how other modules may use its services - typically, other modules
will provide a DataObject
type which subclasses
it. NetBeans programmers use this internal API to implement
Java-based types such as forms, as well as to
provide plug-in functionality such as JavaBeans support.
The reason these calls are not part of the APIs is because no API to them has been carefully thought out and documented. The details of the calls could change greatly from one version to the next, and nobody is tracking such changes. So you can use these module services, but you are on your own if there is an incompatibility. Naturally, it is preferable to use only the Open APIs if that is possible; in some cases it is unreasonable to make this restriction.
Note that any module, not just a standard NetBeans module, can provide an API to other modules. That is, you may decide to release a module which has a published API. Then other modules can access that API, providing they specify dependencies on it using manifest versioning according to the Modules API.
No module should use APIs from the IDE core (i.e. API implementation), however, and no APIs for core classes will be published. The only NetBeans module which connects directly to the core (partially bypassing the Open APIs) is the Auto Update module, since the APIs do not specify any mechanism for querying or modifying the set of installed modules (by design); so, this module depends on specifics of the NetBeans core implementation. Similarly, the API Support and Scripting modules use one call in the core to install test modules.
A: You do not need to do this! Using the Java Hierarchy
API, you can find class elements (see
above),
which are automatically parsed from source code, examine their
contents such as methods, and then change any of these
contents - the code in the Editor will change automatically. Just
remember to save the file afterwards if necessary, using a
SaveCookie
(gotten from the data object gotten from the source element). The
bodies of methods and so on are not parsed, but you can still set
them without needing to find the method in the source
code. Properties such as access modifiers can be adjusted without
any need for parsing or generating Java source - the element
implementation handles this for you.
Note that modification, and saving, is possible even if the file
is not visually open in an Editor window. To make sure the file is
visible, use
OpenCookie.open()
.
If you really want direct access to the text of a file, you can
use
EditorCookie.getDocument()
and then use the Swing API to manipulate it. (But please use
NbDocument.runAtomicAsUser(...)
!)
The Java Hierarchy API should normally synchronize with this
document automatically, according to the parser timeout.
A: Yes. See the Modules API which describes how to include JavaHelp documentation in a module under Help | Contents; and you can provide rich context help rather easily, linking into the same documentation.
A: First of all, manifest file processing tools are according to the JAR specification required to accept and ignore unrecognized attributes - so if you mistyped the name of an attribute (they should be case-insensitive) the IDE will not inform you of this. If you supplied an invalid value, however, or some sort of runtime problem occurred (e.g. a specified instance in the JAR did not implement a required interface), then the IDE should produce an error message - if it does not, that is a bug which should be reported through the normal channels.
Using the API Support module, you may browse to module manifests in the Explorer and they should be recognized as such. Select the node and check its properties and subnodes in the Explorer; you can see at a glance how the IDE is parsing your manifest, and if there are any errors.
The most common error is forgetting that there is a distinction
between global attributes (specified in the head of the
manifest before any blank lines) and manifest sections
with their own attributes (specified in individual blocks after the
header, with blank lines separating them), which are only treated
specially by the IDE if they contain the attribute
OpenIDE-Module-Section
. Please see the
Modules API
for details and example manifests.
ExplorerManager
?
A: You can do that. If you can somehow find a class
implementing
ExplorerManager.Provider
then you can get the Explorer manager. This provider might in fact
be a
TopComponent
in the
TopComponent.Registry
,
if for example it was actually a
ExplorerPanel
.
But this is bad style - for example, if someone wrote a
TopComponent
that included an
ExplorerPanel
only as a subcomponent, and manually
managed the node selection, this trick would fail.
Rather, if you know which top component you care about, you can
just call
TopComponent.getActivatedNodes()
and this will work correctly even for non-Explorer components with
a node selection, such as Editor panes open on Java sources.
Better still is to be agnostic about which top component should
be providing the activated nodes, and just listen to changes in the
TopComponent.Registry.PROP_ACTIVATED_NODES
(or
TopComponent.Registry.PROP_CURRENT_NODES
as appropriate).
But best of all is not to have to ever directly pay attention to
the node selection. If you only need to know the node selection in
order to make some user action enabled or not, you should simply
extend
NodeAction
;
this class does all the dirty work for you of listening to changes
in the node selection and updating its state automatically.
A: No, the
TopManager.getGlobalKeymap()
is a master keymap for the whole IDE, and like all keymaps accepts
only one action per binding. If you want multiple actions to be
run, you must create a "wrapper" action that runs them all in turn
(or in parallel).
You may bind a key differently in different windows, by using the normal Swing techniques of binding keystrokes to components. In fact, some work when into implementing the global map so that it would work across arbitrary components; it is overridden by local bindings, such as navigation keys on dialogs or Explorer trees, or various editing keys in the Editor.
Before you bind a key performing a high-level specific action,
such as F9 for
CompileAction
,
to a different action in a local component (e.g. window), think
carefully whether this is really the right approach. In many cases
the UI of your extension and the IDE as a whole will be better
served by leaving the key binding alone, and instead providing an
appropriate cookie, action performer, or other callback associated
with your component, so that the action (and potentially other code
unknown to you) will function naturally. If you must rebind a
global key, consider whether it is appropriate to determine the
current key binding for the action (if any) in the global keymap,
and use this keystroke to rebind - so user customizations will
remain intact.
A: Not easily. You have a few options:
Add an entry to ide.cfg. For example:
-cp:a c:\eak\lib\eak.jar
This startup file, on Windows installations, provides the ability to add classpath entries to the IDE's Java invocation. On Unix, such a file is not read so other techniques might be needed.
Pros: Adds the library as desired. Cons: Very fragile. Assumes that the ide.cfg is actually read, which is not always true (it is not read by the *.bat scripts or the Unix scripts, for example). Easy to clobber existing user customizations. Dependent on the exact structure of the IDE's bin/ directory, which has changed quite a bit in the past and could change again. Places library in startup classpath, whereas it is preferred to have it in the module classloader only. Must be done before the IDE starts, requiring some kind of manual installation step and making updating your module very difficult.
Duplicate eak.jar in modules/ext/. That is, ship a copy of the required JAR file inside the IDE installation, and continue to refer to dynamic resources in the desired external location.
Pros: Simple to implement and should be reliable. Version of the library shipped can be controlled to match that which the module was compiled against, avoiding potential version skew. Cons: Impractical when the library is physically very large, or there are licensing issues involved in redistributing it. Bugfix upgrades to the master library may not be reflected in the IDE's copy.
Partition your module and use a new classloader. In
other words: logically divide your module into two halves. The first
half will form a compilation unit depending on the Open APIs and will
contain all of the classes referred to directly in the module manifest
(the installer, data loaders, or whatever you have). It should contain
one or more interfaces or similar constructs which describe what it
expects the second half to implement. The second half will form a
separate compilation unit, depending on the first half, possibly the
Open APIs, and also eak.jar. It should contain
implementations of the interfaces specified in the first half. The
first half, at install/restore time (or lazily when any functionality
is needed) should create a new URLClassLoader
whose
parent should be the first half's classloader, and including as URLs
the locations of both the second half as a JAR and the library JAR; using this
classloader, look up the implementation classes by name; create new
instances of them; cast them to the interface types; and begin using
them. The second half should probably be placed in a separate JAR file; if
not, it will be necessary to subclass the dynamic classloader to not delegate
class loads for the implementation packages to its parent. Either way, it is
strongly recommended that the build process enforce that the first half compile
without reference to the second.
Pros: Compliant with the Open APIs and reliable. The library JAR may be located anywhere at runtime and could even be moved or replaced at runtime. Cons: Potentially complex to implement. Some use of reflection required, though it should be safe. More complex build and test procedure for the module. The partition between the two halves must be carefully chosen, especially for large modules, to minimize the complexity of the interfaces and push as much implementation as possible to one side or the other.
Just call
TopManager.showUrl(URL)
to open the IDE's default web browser (internal or external) on some
page. If you want to make a menu item to do this, just create a
*.url file containing the URL and place it in a menu
folder (this only works with the User Utilities module installed,
note).
HtmlBrowser
and its inner classes provide more powerful options.