Intermodule Communications in NetBeans Platform: Open a TopComponent in one module from another TopComponent in another module through the use of Lookup API

In this blog, I’d like to share my experience on an implementation of a NetBeans Platform application. The content below is mostly based on the CRUD tutorial with some of my own modifications. The key idea is on how to use Lookup API for intermodule communications, specifically, for opening a TopComponent in one module from within another TopComponent in another different module without setting up a direct dependency between two of them. Observe the differences of how the Lookup API is used in the ContractTC and in the ContractSummaryTC. So, have fun!

The scenario for this blog is that:

  • I am developing a NetBeans Platform application containing one data class called Contract and three user-interface (UI) windows named: MainTC, ContractTC and ContractSummaryTC.
    • Contract is a POJO class that contains methods and a number of attributes describing a contract such as contract_number, contract_date, and so on. It is created in its own module named Contract module.
    • MainTC is an entry point of an application. Here, a user can specify whether to create a new contract, or to display information of an existing contract.
    • ContractTC is a user-interface window that contains Form fields (JTextFields) for a user to enter information of a new contract.
    • ContractSummaryTC is a user-interface window that contains Swing components (JTextFields) displaying information of an existing contract.
  • Each UI window is created as a TopComponent in its own module. In other words, MainTC is created in main_editor module, ContractTC is created in contract_editor module, and ContractSummaryTC is created in contract_summary_editor module.
  • A user can go from
    • MainTC to ContractTC for entering contract information to create a new contract.
    • MainTC to ContractSummaryTC for displaying an existing contract information including its payment plan based on contract_number provided by a user.
    • ContractTC to ContractSummaryTC for displaying information of the newly-created contract such as the contract itself and its payment plan.
  • ContractTC requires the last contract number from MainTC in order to generating a new contract number.
  • ContractSummaryTC requires a Contract class object from MainTC. The Contract class object is retrieved from a database by using the contract number specified by a user at the MainTC.
  • ContractSummaryTC requires a Contract class object from ContractTC. The Contract class object is a new contract created from information provided by a user in ContractTC.

Use Case #1: Create a new contract.

  1. In MainTC, a user clicks on the “Create a new contract” button.
  2. The system finds the last contract number in a database.
  3. The system generates a new contract number based on the last contract number found.
  4. The system opens the ContractTC window with a new contract number.
  5. In ContractTC window, the user enters all necessary information for a new contract.
  6. The user clicks on the “Save” button.
  7. Go To Use Case #2

Use Case #2: Display information of a newly-created contract.

  1. Continued from Use Case #1.
  2. The system derives a payment plan for the new contract based on user-provided information.
  3. The system opens the ContractSummaryTC window with contract information and its derived payment plan.

Use Case #3: Display information of an existing contract.

  1. In MainTC, a user provides a contract number, and click on the “Find contract” button.
  2. The system searches for a contract in a database based on the contract number.
  3. The system opens the ContractSummaryTC window with information of the contract retrieved from the database.

Implementation guidelines:

  • MainTC, ContractTC, and ContractSummaryTC are in separate modules. For a loosely-coupling reason, we will not set dependency on any pair of these modules. However, all of them have direct dependencies on the Contract module.
  • When creating a new contract, MainTC will add a new contract number into its lookup, and open the ContractTC window. Then, the ContractTC window has to look for the new contract number in the lookup of the MainTC, when it is opened.
  • When displaying an existing contract, MainTC will add a contract class object into its lookup, and open the ContractSummary window. In addition, the ContractSummary window will have to look for a contract class object in the lookup of the MainTC, when it is opened.
  • When displaying a newly-created contract, ContractTC will add a newly-created contract class into its lookup, and open the ContractSummary window. Moreover, the ContractSummary window will look for a contract class object in the lookup of the ContractTC, when it is opened.

MainTC implementation:

  1. Declare InstanceContent variable as an instance variable. The InstanceContent is a dynamic object class that allows modifications of the content in a lookup.

    private InstanceContent content;

  2. Instantiate the InstanceContent variable in the MainTC constructor.

    content = new InstanceContent();

  3. Put the InstanceContent variable into the lookup of the MainTC by adding the following line after the line above in the MainTC constructor.

    associateLookup(new AbstractLookup(content));

  4. In a method in MainTC class that contains a new contract number, add the following lines to add the new contract number to the dynamic object in the lookup and to open the ContractTC window.

    String contractNumber = generateANewContractNumber();
    //Open the ContractTC window where "ContractTC" is its Preferrred_ID.
    TopComponent tc = WindowManager.getDefault().findTopComponent("ContractTC");
    this.content.add(contractNumber);
    if (tc != null) {
       tc.open();
       tc.requestActive();
    }
    this.content.remove(contractNumber);

  5. In a method in MainTC that contains an existing contract class object, add the following lines to add the object to the lookup of MainTC and to open the ContractSummaryTC window.

    Contract contractObj = retrieveContractFromDatabase(contractNumber);
    TopComponent tc = WindowManager.getDefault().findTopComponent("ContractSummaryTC");
    if (tc != null) {
       tc.open();
       tc.requestActive();
    }
    this.content.add(contractObj);
    this.content.remove(contractObj);


    Note #1: I add the contract class object to the lookup of MainTC after I open the ContractSummaryTC window, and then remove the object from the lookup of MainTC. I do not know the reason, but if I add the contract class object to the lookup of MainTC before I open the ContractSummaryTC window, it does not work.
    Note #2: The above implementations are pretty much what we need to do for adding things to the lookup of a TopComponent (MainTC) in one module. So, other modules can use them.
    Note #3: In summary, we add a contract number as String data type and a contract object as Contract class type to the lookup of MainTC. The first is for ContractTC and the later is for ContractSummaryTC.

ContractTC implementation:

  1. Modify the ContractTC class signature to implement LookupListener as shown below.


    public final class ContractTC extends TopComponent implements LookupListener {
    ...
    ...
    }

  2. Declare a Contract object as an instance variable.

    Contract contractObj=null;

  3. Declare a Lookup.Result variable as an instance variable.

    private Lookup.Result result = null;

  4. Declare an InstanceContent variable as an instance variable.

    private InstanceContent content = null;

  5. Add DocumentListener to JTextFields for save functionality in the constructor.

    this.jTextFieldContractNumber.getDocument().addDocumentListener(new DocumentListener(){
      @Override
      public void insertUpdate(DocumentEvent e) {
        fire(true);
      }
      @Override
      public void removeUpdate(DocumentEvent e) {
        fire(true);
      }
      @Override
      public void changedUpdate(DocumentEvent e) {
        fire(true);
      }
    });

  6. Declare a SaveCookie variable as an instance variable.

    private SaveCookie impl = null;

  7. Instantiate the SaveCookie

    impl = new SaveCookieImpl();

  8. Instantiate the InstanceContent variable in the constructor

    content = new InstanceContent();

  9. Put the InstanceContent object into the lookup of the ContractTC.

    associateLookup(new AbstractLookup(content));

  10. Define an inner private class named SaveCookieImpl that implements SaveCookie interface.


    private class SaveCookieImpl implements SaveCookie {
      @Override
      @SuppressWarnings("unchecked")
      public void save() throws IOException {
      //Implement this method.
      }

  11. Implement the save() method in the SaveCookieImpl class.


    @Override
    @SuppressWarnings("unchecked")
    public void save() throws IOException {
      Confirmation message = new NotifyDescriptor.Confirmation("Do you want to save changes?",
    NotifyDescriptor.OK_CANCEL_OPTION, NotifyDescriptor.QUESTION_MESSAGE);
      Object result = DialogDisplayer.getDefault().notify(message);
      if (NotifyDescriptor.YES_OPTION.equals(result)) {
        fire(false);
        //Obtain contract information from user interface such as JTextFields.
        //Create a contract class object from the obtained information.
        contractObj=createContractObjFromObtainedData();
        //May save the contract class object into a database.
        //Open the ContractSummaryTC window below.
        TopComponent tc = WindowManager.getDefault().findTopComponent("ContractSummaryTC");
        if (tc != null) {
          tc.open();
          tc.requestActive();
        }
        content.add(contractObj);
        content.remove(contractObj);
      }
      else
      {
        javax.swing.JOptionPane.showMessageDialog(null, "Unsuccessfully add a new contract.");
      }
    }

  12. Implement the fire() method that is called in the save() method above.

    public void fire(boolean modified) {
      if (modified) {
        //If the text is modified,
        //we add SaveCookie impl to Lookup
          content.add(impl);
        } else {
        //Otherwise, we remove the SaveCookie impl from the lookup
          content.remove(impl);
        }
      }
    }

  13. Override the componentOpened() method as follows.

    result = WindowManager.getDefault().findTopComponent("MainTC").getLookup().lookupResult(String.class);
    result.addLookupListener(this);
    resultChanged(new LookupEvent(result));

  14. Override the componentClosed() method as follows.

    result.removeLookupListener(this);
    result = null;

  15. Override the componentDeactivated() method as follows.

    @Override
    protected void componentDeactivated() {
      this.content.remove(contractObj);
    }

  16. Override the resultChanged() method as follows.

    @Override
    @SuppressWarnings("unchecked")
    public void resultChanged(LookupEvent le) {
      Lookup.Result r = (Lookup.Result) le.getSource();
      Collection col = r.allInstances();
      if (!col.isEmpty()) {
        for (String contractNumber : col) {
          this.jTextFieldContractNumber.setText(contractNumber);
          //Do anything that you would like to.
        }
      }
    }

ContractSummaryTC implementation:

  1. Define two LookupListener classes as private inner classes. These two custom classes are used to detect changes of the content in the lookups of MainTC and ContractTC, respectively.


    private class MyMainTCLookupListener implements LookupListener {
      @Override
      @SuppressWarnings("unchecked")
      public void resultChanged(LookupEvent le) {
        Lookup.Result r = (Lookup.Result) le.getSource();
        Collection col = r.allInstances();
        if (!col.isEmpty()) {
         for (ContractT cont : col) {
          jTextFieldContractNumber.setText(cont.getContractNumber());
          jTextFieldContractDate.setText(cont.getContractDate());
          //Retrieve payment history, or
          //Do something else as you would like to.
         }
        }
       }
    }


    private class MyContractTCLookupListener implements LookupListener {
      @Override
      @SuppressWarnings("unchecked")
      public void resultChanged(LookupEvent le) {
        Lookup.Result r = (Lookup.Result) le.getSource();
        Collection col = r.allInstances();
        if (!col.isEmpty()) {
         for (ContractT cont : col) {
          jTextFieldContractNumber.setText(cont.getContractNumber());
          jTextFieldContractDate.setText(cont.getContractDate());
          //Generate a payment plan, or
          //Do something else as you would like to.
         }
        }
       }
    }

    Note: The reason I define two separate LookupListener classes is that I can differentiate where or which class the Contract object is coming from. For example, if a contract object is from MainTC, I know that the contract is retrieved from the database. Accordingly, I can further retrieve payment history for this contract. In contrary, if a contract object is from ContractTC, I know that it is a newly-created contract. So, I can further implement a generation of payment plan for this contract.

     

  2. Declare two LookupListener variables as instance variables.

    private MyMainTCLookupListener mainTCLookupListener;
    private MyContractTCLookupListener contractTCLookupListener;

  3. Declare two Lookup.Result variables as instance variables.

    private Lookup.Result mainTCLookupResult;
    private Lookup.Result contractTCLookupResult;

  4. Instantiate LookupListener variables in the constructor.


    mainTCLookupListener = new MyMainTCLookupListener();
    contractTCLookupListener = new MyContractTCLookupListener();

  5. Override the componentOpened() method as follows.

    mainTCLookupResult = WindowManager.getDefault().findTopComponent("MainTC").getLookup().lookupResult(ContractT.class);
    mainTCLookupResult.addLookupListener(mainTCLookupListener);
    contractTCLookupResult = WindowManager.getDefault().findTopComponent("ContractTC").getLookup().lookupResult(ContractT.class);
    contractTCLookupResult.addLookupListener(contractTCLookupListener);

  6. Override the componentClosed() method as follows.

    mainTCLookupResult.removeLookupListener(mainTCLookupListener);
    mainTCLookupResult = null;
    contractTCLookupResult.removeLookupListener(contractTCLookupListener);
    contractTCLookupResult = null;

Advertisements
This entry was posted in Uncategorized. Bookmark the permalink.

2 Responses to Intermodule Communications in NetBeans Platform: Open a TopComponent in one module from another TopComponent in another module through the use of Lookup API

  1. james says:

    Good use scenario explanation, but where is code? or is the code same as the CRUD tutorial ?

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s