Platinum Partner
netbeans

Step-by-Step Instructions for Integrating DJ Native Swing into NetBeans RCP

Here's how to integrate DJ Native Swing into a NetBeans RCP application. We will create multiple operating-system specific modules, each with the JARs and supporting classes needed for the relevant operating system.

Then, in a module installer, we will enable only the module that is relevant for the operating system in question. I.e., if the user is on Windows, only the Windows module will be enabled, while all other modules will be disabled.

Many thanks to Aljoscha Rittner for all of the code and each of the steps below. Any errors are my own, his instructions are perfect.

1. Download DJ Native Swing.
 
2. Go to download.eclipse.org/eclipse/downloads/drops/R-3.6-201006080911/. There, under the heading "SWT Binary and Source", download and extract the os-specific ZIPs that you want to support.

3. Unzip your downloaded ZIPs. Rename your swt.jar in the unzipped files to the full name of the zip. For example, instead of multiple "swt.jar" files, you'll now have JAR names such as "swt-3.6-gtk-linux-x86_64.jar" and "swt-3.6-win32-win32-x86_64.jar". Because these JARs will be in the same cluster folder in the NetBeans Platform application, they will need to have different names.

4. Let's start with Linux. Put the two DJ Native Swing JARs ("DJNativeSwing.jar" and "DJNativeSwing-SWT.jar") into a NetBeans library wrapper module named "com.myapp.nativeswing.linux64". Also put the "swt-3.6-gtk-linux-x86_64.jar" into the library wrapper module, while checking the "project.xml" and making sure there's a classpath extension entry for each of the three JARs in your library wrapper module.

5. Do the same for all the operating systems you're supporting, i.e., create a new library wrapper module like the above, with the operating-system specific SWT Jar, together with the two DJ Native Swing JARs.

6. Create a new module named "DJNativeSwingAPI", with code name base "com.myapp.nativeswing.api".

7. In the above main package, create a subpackage "browser", where you'll create an API to access the different implementations:

public interface Browser {
    public JComponent getBrowserComponent();
    public void browseTo (URL url);
    public void dispose();
}
public interface BrowserProvider {
    public Browser createBrowser();
}

8. Make the "browser" package public and let all the operating-system specific library wrapper modules depend on the API module.

9. In each of the operating-system specific modules, create an "impl" subpackage with the following content, here specifically for Windows 64 bit:

package com.myapp.nativeswing.windows64.impl;
import chrriis.dj.nativeswing.swtimpl.NativeInterface;
import com.myapp.nativeswing.api.browser.Browser;
import com.myapp.nativeswing.api.browser.BrowserProvider;
import org.openide.util.lookup.ServiceProvider;

@ServiceProvider(service = BrowserProvider.class)
public class Win64BrowserProvider implements BrowserProvider {

    private boolean isInitialized;

    @Override
    public Browser createBrowser() {
        initialize();
        return new Win64Browser();
    }

    private synchronized void initialize() {
        if (!isInitialized) {
            NativeInterface.open();
            isInitialized = true;
        }
    }

}
import chrriis.dj.nativeswing.NSComponentOptions;
import chrriis.dj.nativeswing.swtimpl.components.JWebBrowser;
import com.myapp.nativeswing.api.browser.Browser;
import java.net.URL;
import javax.swing.JComponent;

class Win64Browser implements Browser {

    private JWebBrowser webBrowser;
    public Win64Browser() {

        //If not this, browser component creates exceptions when you move it around,
        //this flag is for the native peers to recreate in the new place:
        webBrowser = new JWebBrowser(NSComponentOptions.destroyOnFinalization());

    }

    public JComponent getBrowserComponent() {
        return webBrowser;
    }

    public void browseTo(URL url) {
        webBrowser.navigate(url.toString());
    }

    public void dispose() {
        webBrowser.disposeNativePeer();
        webBrowser = null;
    }   

}

10. Copy the above two classes into all your other operating-system specific library wrapper modules. Rename the classes accordingly.

11. In the DJ Native Swing API module, create a new subpackage named "utils", with this class, which programmatically enables/disables modules using the NetBeans AutoUpdate API:

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.netbeans.api.autoupdate.OperationContainer;
import org.netbeans.api.autoupdate.OperationContainer.OperationInfo;
import org.netbeans.api.autoupdate.OperationException;
import org.netbeans.api.autoupdate.OperationSupport;
import org.netbeans.api.autoupdate.OperationSupport.Restarter;
import org.netbeans.api.autoupdate.UpdateElement;
import org.netbeans.api.autoupdate.UpdateManager;
import org.netbeans.api.autoupdate.UpdateUnit;
import org.openide.LifecycleManager;
import org.openide.modules.ModuleInfo;
import org.openide.util.Exceptions;
import org.openide.util.Lookup;

/**
* Der ModuleHandler ist eine Hilfsklasse zum programatischen (de)aktivieren
* von Modulen und der Analyse von installierten aktiven Modulen.
* @author rittner
*/
public class ModuleHandler {

  private boolean restart = false;
  private OperationContainer<OperationSupport> oc;
  private Restarter restarter;
  private final boolean directMode;


  public ModuleHandler() {
    this (false);
  }
  public ModuleHandler(boolean directMode) {
    this.directMode = directMode;
  }

  /**
   * Gibt eine sortierte Liste der Codename-Base aller aktiven installierten
   * Module zurück.
   * <p>
   * Es handelt sich dabei explizit um einen aktuellen Zwischenstand, der sich
   * jeder Zeit verändern kann.
   * @param startFilter Es werden nur die Module zurückgegeben, die mit dem Startfilter-Namen anfangen (oder null für alle)
   * @param includeDisabled Wenn true, werden auch alle inaktiven Module ermittelt.
   * @return Sortierte Liste der Codename-Base
   */
  public List<String> getModules(String startFilter, boolean includeDisabled) {
    List<String> activatedModules = new ArrayList<String>();
    Collection<? extends ModuleInfo> lookupAll = Lookup.getDefault().lookupAll(ModuleInfo.class);
    for (ModuleInfo moduleInfo : lookupAll) {
      if (includeDisabled || moduleInfo.isEnabled()) {
        if (startFilter == null || moduleInfo.getCodeNameBase().startsWith(startFilter)) {
          activatedModules.add(moduleInfo.getCodeNameBase());
        }
      }
    }
    Collections.sort(activatedModules);
    return activatedModules;
  }


  /**
   * Führt einen Neustart der Anwendung durch, wenn der vorherige setModulesState
   * ein Flag dafür gesetzt hat. mit force, kann der Restart erzwungen werden.
   * <p>
   * Man sollte nicht davon ausgehen, dass nach dem Aufruf der Methode
   * zurückgekehrt wird.
   * @param force
   */
  public void doRestart(boolean force) {
    if (force || restart) {
      if (oc != null && restarter != null) {
        try {
          oc.getSupport().doRestart(restarter, null);
        } catch (OperationException ex) {
          Exceptions.printStackTrace(ex);
        }
      } else {
        LifecycleManager.getDefault().markForRestart();
        LifecycleManager.getDefault().exit();
      }
    }
  }

  /**
   * Aktiviert oder deaktivert die Liste der Module
   * @param enable
   * @param codeNames
   * @return true, wenn ein Neustart zwingend erforderlich ist
   */
  public boolean setModulesState (boolean enable, Set<String> codeNames) {
    boolean restartFlag;
    if (enable) {
      restartFlag = setModulesEnabled(codeNames);
    } else {
      restartFlag = setModulesDisabled(codeNames);
    }
    return restart = restart || restartFlag;
  }

  private boolean setModulesDisabled(Set<String> codeNames) {
    Collection<UpdateElement> toDisable = new HashSet<UpdateElement>();
    List<UpdateUnit> allUpdateUnits =
            UpdateManager.getDefault().getUpdateUnits(UpdateManager.TYPE.MODULE);
    for (UpdateUnit unit : allUpdateUnits) {
      if (unit.getInstalled() != null) {
        UpdateElement el = unit.getInstalled();
        if (el.isEnabled()) {
          if (codeNames.contains(el.getCodeName())) {
            toDisable.add(el);
          }
        }
      }
    }


    if (!toDisable.isEmpty()) {
      oc = directMode ? OperationContainer.createForDirectDisable() : OperationContainer.createForDisable();
      for (UpdateElement module : toDisable) {
        if (oc.canBeAdded(module.getUpdateUnit(), module)) {
          OperationInfo operationInfo = oc.add(module);
          if (operationInfo == null) {
            continue;
          }
          // get all module depending on this module
          Set<UpdateElement> requiredElements =
                  operationInfo.getRequiredElements();
          // add all of them between modules for disable
          oc.add(requiredElements);
        }
      }


      try {
        // get operation support for complete the disable operation
        OperationSupport support = oc.getSupport();
        // If support is null, no element can be disabled.
        if ( support != null ) {
          restarter = support.doOperation(null);
        }
      } catch (OperationException ex) {
        Exceptions.printStackTrace(ex);
      }
    }
    return restarter != null;

  }

  private boolean setModulesEnabled(Set<String> codeNames) {
    Collection<UpdateElement> toEnable = new HashSet<UpdateElement>();
    List<UpdateUnit> allUpdateUnits =
            UpdateManager.getDefault().getUpdateUnits(UpdateManager.TYPE.MODULE);
    for (UpdateUnit unit : allUpdateUnits) {
      if (unit.getInstalled() != null) {
        UpdateElement el = unit.getInstalled();
        if (!el.isEnabled()) {
          if (codeNames.contains(el.getCodeName())) {
            toEnable.add(el);
          }
        }
      }
    }


    if (!toEnable.isEmpty()) {
      oc = OperationContainer.createForEnable();
      for (UpdateElement module : toEnable) {
        if (oc.canBeAdded(module.getUpdateUnit(), module)) {
          OperationInfo operationInfo = oc.add(module);
          if (operationInfo == null) {
            continue;
          }
          // get all module depending on this module
          Set<UpdateElement> requiredElements =
                  operationInfo.getRequiredElements();
          // add all of them between modules for disable
          oc.add(requiredElements);
        }
      }


      try {
        // get operation support for complete the enable operation
        OperationSupport support = oc.getSupport();
        if (support != null) {
          restarter = support.doOperation(null);
        }
        return true;
      } catch (OperationException ex) {
        Exceptions.printStackTrace(ex);
      }
    }
    return false;

  }

}
12. Create a ModuleInstall class in the API module. In this class, we need to create a map, connecting all the operating systems to the related code name base of the module relevant to the specific operating system. For this, we use "os.arch" and "os.name". Then we create an enable list and a disable list for the code name base. We create two handlers, one to disable everything, the other to enable just the relevant module.
public class Installer extends ModuleInstall {

    @Override

    public void restored() {

        Map modelMap = new HashMap();
        modelMap.put("Windows.64", "com.myapp.nativeswing.windows64");
        modelMap.put("Linux.64", "com.myapp.nativeswing.linux64");

        String osArch = System.getProperty("os.arch");
        if ("amd64".equals(osArch)) {
            osArch = "64";
        } else {
            osArch = "32";
        }
       
        String osName = System.getProperty("os.name");
        if (osName.startsWith("Windows")) {
            osName = "Windows";
        }
        if (osName.startsWith("Mac")) {
            osName = "Mac";
        }

        Map osNameMap = new HashMap();
        osNameMap.put("Windows", "Windows");
        osNameMap.put("Linux", "Linux");
        osNameMap.put("Mac", "Mac");

        String toEnable = modelMap.get(osNameMap.get(osName) + "." + osArch);
        Set toDisable = new HashSet(modelMap.values());
        if (toEnable != null) {
            toDisable.remove(toEnable);
        }

        ModuleHandler disabler = new ModuleHandler(true);
        disabler.setModulesState(false, toDisable);

        ModuleHandler enabler = new ModuleHandler(true);
        enabler.setModulesState(true, Collections.singleton(toEnable));

    }

}
13. Finally, create yet another module, where the TopComponent will be found that will host the browser from DJ Native Swing. So, create a new module, add a window where the browser will appear, and set a dependency on the DJ Native Swing API module.

In the constructor of the window add the following:
setLayout(new BorderLayout());
BrowserProvider bp = Lookup.getDefault().lookup(BrowserProvider.class);
if (bp!=null){
    Browser createBrowser = bp.createBrowser();
    add(createBrowser.getBrowserComponent(), BorderLayout.CENTER);
}
14. By default, library wrapper modules are set to "1.4" source code level and to "autoload". You will need to change "1.4" to "1.6" (since you're using annotations above). You will also need to change "autoload" to "regular", otherwise they will never be loaded, since no module depends on them.

15. On Linux, at least on Ubuntu, make sure you have done something like this:
export MOZILLA_FIVE_HOME=/usr/lib/mozilla
export LD_LIBRARY_PATH=$MOZILLA_FIVE_HOME 

On Linux (at least on Ubuntu), you also need to set an impl dependency on the "JNA" module.

16. In "platform.properties", add this line:

run.args.extra=-J-Dsun.awt.disableMixing=true

Hurray, you're done, once you run the application:

Note: above I followed these instructions to remove the tab in the browser window.

{{ tag }}, {{tag}},

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}
{{ parent.authors[0].realName || parent.author}}

{{ parent.authors[0].tagline || parent.tagline }}

{{ parent.views }} ViewsClicks
Tweet

{{parent.nComments}}