Netbeans Platform parsing layer.xml
September 20th, 2007 | by Tonny Kohar |If you are building application on top of Netbeans Platform sooner or later you will deal with layer.xml. Layer.xml (Netbeans FAQ) is
Layer files are small XML files provided by modules, which define a virtual filesystem . The layer file defines folders and files that will be merged into the system filesystem that makes up the runtime configuration information NetBeans and its modules use.
Layer files help to make it possible for modules to be dynamically installed. If you’ve read about FileObjects and FileSystems , you know that you can listen for changes in folders and files in a filesystem. That’s exactly what the components of NetBeans whose content is composed from folders in the system filesystem do. So if a module is added at runtime, the system filesystem fires changes; the UI notices that the contents of the folder has changed and updates the UI to reflect the changes.
On the Citra FX Photo Filter, if you look at the Effects TopComponent there is a button filter, if you click that button it will display popup menu which have the same menu item as the usual Menu – Filter at the top. In that particular case, we are parsing the layer.xml and build the popup menu. Below is the java code to parse the Netbeans layer.xml
Note: this is tested on Netbeans IDE 5.5.1 and Netbeans IDE 6 M10
layer.xml content
Here is the sample layer.xml node that we are interested to parse. The node is Menu/Filter and its children
<filesystem> ... <folder name="Menu"> <folder name="Filter"> <folder name="Blur"> <attr name="SystemFileSystem.localizingBundle" stringvalue="kiyut.citra.Bundle"/> <file name="kiyut-citra-modules-fxfilter-action-filter-BoxBlurFXFilterAction.shadow"> <attr name="originalFile" stringvalue="Actions/Filter/kiyut-citra-modules-fxfilter-action-filter-BoxBlurFXFilterAction.instance"/> </file> ... </folder> <folder name="Color"> <attr name="SystemFileSystem.localizingBundle" stringvalue="kiyut.citra.Bundle"/> <file name="kiyut-citra-modules-fxfilter-action-filter-AdjustHSBFXFilterAction.shadow"> <attr name="originalFile" stringvalue="Actions/Filter/kiyut-citra-modules-fxfilter-action-filter-AdjustHSBFXFilterAction.instance"/> </file> <file name="kiyut-citra-modules-fxfilter-action-filter-AdjustRGBFXFilterAction.shadow"> <attr name="originalFile" stringvalue="Actions/Filter/kiyut-citra-modules-fxfilter-action-filter-AdjustRGBFXFilterAction.instance"/> </file> ... </folder> <folder name="Stylize"> <attr name="SystemFileSystem.localizingBundle" stringvalue="kiyut.citra.Bundle"/> <file name="kiyut-citra-modules-fxfilter-action-filter-ChromeFXFilterAction.shadow"> <attr name="originalFile" stringvalue="Actions/Filter/kiyut-citra-modules-fxfilter-action-filter-ChromeFXFilterAction.instance"/> </file> <file name="kiyut-citra-modules-fxfilter-action-filter-ContourFXFilterAction.shadow"> <attr name="originalFile" stringvalue="Actions/Filter/kiyut-citra-modules-fxfilter-action-filter-ContourFXFilterAction.instance"/> </file> <file name="kiyut-citra-modules-fxfilter-action-filter-RaysFXFilterAction.shadow"> <attr name="originalFile" stringvalue="Actions/Filter/kiyut-citra-modules-fxfilter-action-filter-RaysFXFilterAction.instance"/> </file> ... </folder> </folder> ... </folder> ... </filesystem> |
the layer.xml parsing code
This parsing code is basically recursively iterate the layer.xml at the specified node and adding the action instance into our own menu
private static final String FILTER_POPUP_FOLDER_NAME = "Menu/Filter"; /** Build Popup Menu from layer.xml declaration under Menu/Filter/ */ protected void createPopupMenu() { filterPopupMenuValid = true; FileSystem fs = Repository.getDefault().getDefaultFileSystem(); FileObject fo = fs.getRoot().getFileObject(FILTER_POPUP_FOLDER_NAME); if (fo == null) { this.filterPopupMenu = null; return; } filterPopupMenu = new JPopupMenu(); buildFilterPopup(fo,filterPopupMenu); if (filterPopupMenu.getComponentCount() <= 0 ) { this.filterPopupMenu = null; } } /** Recursive Build Popup Menu *@param fo FileObject as the DataFolder *@param comp Menu Container eg: JPopupMenu or JMenu */ private void buildFilterPopup(FileObject fo, JComponent comp) { DataFolder df = DataFolder.findFolder(fo); DataObject[] childs = df.getChildren(); DataObject dob; Object instanceObj; for (int i = 0; i < childs.length; i++) { dob = childs[i]; if (dob.getPrimaryFile().isFolder()) { FileObject childFo = childs[i].getPrimaryFile(); JMenu menu = new JMenu(); Mnemonics.setLocalizedText(menu,dob.getNodeDelegate().getDisplayName()); comp.add(menu); buildFilterPopup(childFo,menu); } else { InstanceCookie ck = (InstanceCookie)dob.getCookie(InstanceCookie.class); try { instanceObj = ck.instanceCreate(); } catch (Exception ex) { instanceObj = null; ErrorManager.getDefault().notify(ErrorManager.EXCEPTION,ex); ErrorManager.getDefault().log(ErrorManager.EXCEPTION,ex.getMessage()); } if (instanceObj == null) { continue; } if (instanceObj instanceof JSeparator) { // BUG: JSeparator instance is shared between CanvasTC, ordering will be fail // WORKAROUND: create new JSeparator //comp.add((JSeparator)instanceObj); comp.add(new JSeparator()); } else if (instanceObj instanceof BooleanStateAction) { JCheckBoxMenuItem menuItem = new JCheckBoxMenuItem(); Actions.connect(menuItem,(BooleanStateAction)instanceObj,true); } else if (instanceObj instanceof Action) { JMenuItem menuItem = new JMenuItem(); Actions.connect(menuItem,(Action)instanceObj,true); comp.add(menuItem); } } } } |
Important: there is special case for JSeparator, because the JSeparator instance is shared between the new menu and original menu, you need to created new instance of JSeparator instead of using the existing instance, or the menu ordering will fail
Adding the listener to monitor the layer.xml change
As you already know, Netbeans Platform allows plugins / module to be added or removed at runtime. This capability means that we must listen for the change on the layer.xml incase some module is adding or removing menu item in the node we are interested and rebuild our own menu.
// put this code on the constructor or anything appropriate FileSystem fs = Repository.getDefault().getDefaultFileSystem(); FileObject fo = fs.getRoot().getFileObject(FILTER_POPUP_FOLDER_NAME); // add listener to monitor layer.xml popup menu change fo.addFileChangeListener(new FileChangeAdapter() { public void fileFolderCreated(FileEvent fe) { filterPopupMenuValid = false; } public void fileDataCreated(FileEvent fe) { filterPopupMenuValid = false; } public void fileChanged(FileEvent fe) { filterPopupMenuValid = false; } public void fileDeleted(FileEvent fe) { filterPopupMenuValid = false; } public void fileRenamed(FileRenameEvent fe) { filterPopupMenuValid = false; } public void fileAttributeChanged(FileAttributeEvent fe) { filterPopupMenuValid = false; } }); filterMenuButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent evt) { if (filterPopupMenuValid == false) { createPopupMenu(); } if (filterPopupMenu == null) { return; } int x = filterMenuButton.getWidth(); int y = 0; filterPopupMenu.show(filterMenuButton,x, y); } }); // also do not forget to deregister the listener above, when no longer needed eg: the component is closed, etc |
Tags: Java, Netbeans Platform, Trick
1 Trackback(s)
You must be logged in to post a comment.