IJC Calculator Tutorial

Overview

This tutorial shows how to add an action, invoked by a menu item, for reading values from the grid view selection and calculating averages or sums.

The following sections are covered in this tutorial:

Contents:


Prerequisite Knowledge

This tutorial assumes that you have worked through Getting Started with Extending IJC and that you are familiar with the concepts and instructions described therein. As before, you will use NetBeans IDE to create the sample that will extend IJC. As a result, you are assumed to be in possession of the required software and to know how to use NetBeans IDE to:

No time will be spent on the above topics in this tutorial.

Exploring the Sample

Before we begin creating our module, let's get to know the sample that we will build in this tutorial. The sample is already installed in IJC. Here, we try it out.

  1. Start up IJC and open a JChem entity in grid view.

  2. Select two or more values.

  3. Under the Help menu, notice that the two menu items "Compute average" and "Compute sum" are now enabled:

    Above, the values 169.07 and 202.55 are selected.

  4. When you select either of these menu items, a dialog box pops up, displaying the sum or average of the selected values, depending on which menu item you selected. For example, if you add the two values, the following dialog box pops up:

Now that you know the functionality that this tutorial provides, let's create the functionality from scratch.

Creating the Module Source Structure from Scratch

In this section, we use the New Module wizard, described in Getting Started with Extending IJC, to create a source structure.

  1. Use the New Module template and type 'MathComputations' in 'Project Name'. Click Next.

  2. In the second panel, type 'com.im.ijc.mathcomputations' in 'Code Name Base', which uniquely defines your module. Click Finish.

You now have a source structure for your module.

Setting Dependencies on Other Modules

Our next step is to specify which APIs we want to use. Each API is provided by a module. Some of the APIs belong to the NetBeans APIs, while others belong to the Instant JChem APIs. Because our module is part of the IJC application, which itself makes use of all the NetBeans APIs and IJC APIs that we need, we can set dependencies on the modules that are available to the application.

  1. Right-click the 'MathComputations' module project in the Projects window and choose Properties.

  2. In the Project Properties dialog box, choose Libraries.

  3. From the NetBeans Platform drop-down list, make sure that your IJC installation is selected, as explained in Getting Started with Extending IJC.

  4. Click Add, next to the Module Dependencies list, and choose the following modules from the Add Module Dependency dialog. While doing so, note the Classes column in the table below, because there you can see the classes we will be using from the related API provided by the selected module:

    Module Purpose Classes Relevant to Our Module
    • Provides a set of utility classes for creating dialog boxes.
    • DIF (Discovery Informatics Framework)
    • Provides the data model and persistence tiers for applications that need to use chemical and biological data.
    • Provides the basic user interface for Instant JChem and, through its API, provides the ability to extend the IJC user interface.
    • Provides a way to represent JavaBeans or other property containers, formed into a hierarchical tree.
    • Provides a set of utility classes covering general infrastructure points in the NetBeans Platform.

    You should now see the following:

  5. Click OK.

Creating the Computation Performer Interface

First, let's begin by creating an interface for our computations.

  1. In the main package, 'com.im.ijc.mathcomputations', create a Java class called ComputationPerformer.java.

  2. Replace the default code with the following:
    public interface ComputationPerformer {
    
        public void reset();
        
        public void addValue(Object value);
        
        public Object getResult();
        
        public String getResultDisplayMessage();
        
        public String getProgressName();
        
        public Class[] getSupportedFieldCapabilities();
    }

Creating the Abstract Computation Performer

Next, we'll implement the interface that we created in the previous section.

  1. Again in the main package, create a Java class called AbstractComputationPerformer.java.

  2. Add two import statements:
    import com.im.df.api.capabilities.DFFieldFloatCapability;
    import com.im.df.api.capabilities.DFFieldIntegerCapability;

  3. Replace the default code with the following:
    public abstract class AbstractComputationPerformer implements ComputationPerformer {
    
        private static final Class[] DEFAULT_SUPPORTED_CAPABILITIES 
                    = new Class[] { DFFieldFloatCapability.class, 
                      DFFieldIntegerCapability.class };
    
        private int counter = 0;
        
        public void reset() {
            counter = 0;
        }
    
        public int getCount() {
            return counter;
        }
        
        public void addValue(Object value) {
            counter++;
        }
    
        public Class[] getSupportedFieldCapabilities() {
            return DEFAULT_SUPPORTED_CAPABILITIES;
        }
    
    }

Creating an Abstract Computation Action

We will create an abstract action, which we will use as the basis of both the actions that we create in the following section.

  1. In the same package as before, create a Java class called AbstractComputationAction.java.

  2. Add the following import statements:
    import com.im.df.api.ddl.DFField;
    import com.im.df.api.dml.DFResultSet;
    import com.im.df.api.support.DFFeedback;
    import com.im.df.api.support.SelectionDescription;
    import com.im.df.api.util.DIFUtilities;
    import com.im.df.util.UIBackgroundRunnerRO;
    import com.im.ijc.core.api.actions.AbstractFieldSelectionAwareAction;
    import com.im.ijc.core.api.util.IJCCoreUtils;
    import com.im.ijc.core.api.cookie.IJCWidgetCookie;
    
    import java.text.MessageFormat;
    import java.util.ArrayList;
    import java.util.List;
    import java.util.Map;
    
    import org.openide.DialogDisplayer;
    import org.openide.NotifyDescriptor;
    import org.openide.nodes.Node;
    import org.openide.util.NbBundle;

  3. Replace the default code with the following:
    public abstract class AbstractComputationAction extends AbstractFieldSelectionAwareAction {
    
        private static final int BATCH_MAX_SIZE = 200;
        private static final MessageFormat PROGRESS_MSG = new MessageFormat(NbBundle.getMessage(AbstractComputationAction.class, "MSG_ProgressDetails"));
        
        private ComputationPerformer computer;
        
        public AbstractComputationAction(ComputationPerformer computer) {
            this.computer = computer;
        }
        
        protected void performAction(Node[] nodes) {
            IJCWidgetCookie ijcwc = findOVC(nodes);
            final List<DFField> fields = IJCCoreUtils.computeFields(ijcwc, false, true);
            final DFResultSet.VertexState vs = IJCCoreUtils.computeCommonVS(ijcwc);
    
            UIBackgroundRunnerRO runner = new UIBackgroundRunnerRO(getName(), true) {
    
                public void phase1InRequestProcessor() {
                    computer.reset();
                    
                    SelectionDescription selection = vs.getSelection();
                    int total = selection.getSelectedRowsCount();
                    getEnvironment().getFeedback().switchToDeterminate(total);
                    List ids = new ArrayList();
                    for (int i = selection.getMinSelectionIndex(); i <= selection.getMaxSelectionIndex(); i++) {
                        if (getEnvironment().isCancelled()) {
                            return;
                        }
                        
                        ids.add(vs.getIdAt(i));
                        
                        if ((ids.size() > BATCH_MAX_SIZE) || (i == selection.getMaxSelectionIndex())) {
                            Map<Object, Map<String, Object>> data = vs.getData(ids, getEnvironment());
                            
                            int current = i - selection.getMinSelectionIndex();
                            getEnvironment().getFeedback().addMessage(DFFeedback.Type.PROGRESS_MESSAGE, PROGRESS_MSG.format(new Object[] { current, total }), null);
                            getEnvironment().getFeedback().progress(current);
                            
                            for (Map<String, Object> row: data.values()) {
                                for (DFField f: fields) {
                                    computer.addValue(row.get(f.getId()));
                                }
                                if (getEnvironment().isCancelled()) {
                                    return;
                                }
                            }
                            ids.clear();
                        }
                    }
                    
                    NotifyDescriptor.Message nd = new NotifyDescriptor.Message(computer.getResultDisplayMessage(), NotifyDescriptor.INFORMATION_MESSAGE);
                    DialogDisplayer.getDefault().notifyLater(nd);
                }
                
            };
            runner.start();
        }
    
        @Override
        protected boolean enableAccordingToCurrentSelection(IJCWidgetCookie ijcwc) {
            if (super.enableAccordingToCurrentSelection(ijcwc)) {
                DFResultSet.VertexState vs = IJCCoreUtils.computeCommonVS(ijcwc);
                if (vs == null) {
                    return false;
                }
                
                List<DFField> fields = IJCCoreUtils.computeFields(ijcwc, false, true);
                for (DFField f: fields) {
                    boolean ok = false;
                    for (Class c: computer.getSupportedFieldCapabilities()) {
                        if (DIFUtilities.findCapability(f, c) != null) {
                            ok = true;
                            break;
                        }
                    }
                    if (!ok) {
                        return false;
                    }
                }
                return true;
            }
            return false;
        }
    }

Creating an Action to Compute Sums

Let's now implement the abstract action from the previous section. Here, we implement it to create an action to compute sums.

  1. As before, in the main package, create a Java class called ComputeSumAction.java.

  2. Add the following import statements:
    import java.text.MessageFormat;
    import org.openide.util.NbBundle;

  3. Replace the default code with the following:
    public class ComputeSumAction extends AbstractComputationAction {
    
        private static final MessageFormat RESULT_FORMAT = new MessageFormat(NbBundle.getMessage(ComputeSumAction.class, "MSG_SumResult"));
        
        public ComputeSumAction() {
            super(new SumComputer());
        }
        
        public String getName() {
            return NbBundle.getMessage(ComputeSumAction.class, "NAME_Sum");
        }
    
        private static class SumComputer extends AbstractComputationPerformer {
            private double sum = 0;
            
            @Override
            public void reset() {
                super.reset();
                sum = 0;
            }
    
            @Override
            public void addValue(Object value) {
                super.addValue(value);
                Number n = (Number) value;
                sum += n.doubleValue();
            }
    
            public String getResultDisplayMessage() {
                return RESULT_FORMAT.format(new Object[] { getCount(), getResult() } );
            }
            
            public Object getResult() {
                return sum;
            }
    
            public String getProgressName() {
                return NbBundle.getMessage(ComputeSumAction.class, "MSG_ProgressComputingSum");
            }
        }
    }

Creating an Action to Compute Averages

Let's now implement the abstract action from the previous section. Here, we implement it to create an action to compute averages.

  1. Create a Java class, again in the main package, called ComputeAverageAction.java.

  2. Add the following import statements:
    import java.text.MessageFormat;
    import org.openide.util.NbBundle;

  3. Replace the default code with the following:
    public class ComputeAverageAction extends AbstractComputationAction {
    
        private static final MessageFormat RESULT_FORMAT = new MessageFormat(NbBundle.getMessage(ComputeSumAction.class, "MSG_AvgResult"));
        
        public ComputeAverageAction() {
            super(new AverageComputer());
        }
        
        public String getName() {
            return NbBundle.getMessage(ComputeAverageAction.class, "NAME_Avg");
        }
    
        private static class AverageComputer extends AbstractComputationPerformer {
            private double sum = 0;
            
            @Override
            public void reset() {
                super.reset();
                sum = 0;
            }
    
            @Override
            public void addValue(Object value) {
                super.addValue(value);
                Number n = (Number) value;
                sum += n.doubleValue();
            }
    
            public String getResultDisplayMessage() {
                return RESULT_FORMAT.format(new Object[] { getCount(), getResult() } );
            }
            
            public Object getResult() {
                return sum / getCount();
            }
            
            public String getProgressName() {
                return NbBundle.getMessage(ComputeAverageAction.class, "MSG_ProgressComputingAvg");
            }
        }
    }
    

Registering the Strings in the Resource Bundle

The centralized location of our strings is our resource bundle. Let's put them there, in 'Bundle.properties', in the main package:

MSG_SumResult=The sum of {0} selected values is {1}.
NAME_Sum=Compute sum
MSG_ProgressComputingSum=Computing sum...

MSG_AvgResult=The average of {0} selected values is {1}.
NAME_Avg=Compute average
MSG_ProgressComputingAvg=Computing average...

MSG_ProgressDetails=Counting - row {0} out of {1}

Registering the Actions in the Layer File

Finally, let's register our actions in the layer file.

<folder name="Actions">
    <folder name="Tools">
        <file name="com-im-ijc-mathcomputations-ComputeAverageAction.instance"/>
        <file name="com-im-ijc-mathcomputations-ComputeSumAction.instance"/>
    </folder>
</folder>

<folder name="Menu">
    <folder name="Tools">
        <folder name="IJC API examples">
            <file name="com-im-ijc-mathcomputations-ComputeSumAction.shadow">
                <attr name="originalFile" stringvalue="Actions/Tools/com-im-ijc-mathcomputations-ComputeSumAction.instance"/>
                <attr name="position" intvalue="100"/>
            </file>
            <file name="com-im-ijc-mathcomputations-ComputeAverageAction.shadow">
                <attr name="originalFile" stringvalue="Actions/Tools/com-im-ijc-mathcomputations-ComputeAverageAction.instance"/>
                <attr name="position" intvalue="200"/>
            </file>
        </folder>
    </folder>
</folder>

Conclusion

Congratulations, you have created actions for computing sums and averages in IJC. You simply need to run the module, as described earlier, and then IJC is extended with your new feature and you can use it and distribute it to your users.