001    /**
002     * DisplayCasesTableMethod.java
003     * jCOLIBRI2 framework. 
004     * @author Juan A. Recio-García.
005     * GAIA - Group for Artificial Intelligence Applications
006     * http://gaia.fdi.ucm.es
007     * 25/10/2007
008     */
009    package jcolibri.extensions.recommendation.navigationByProposing;
010    
011    import java.awt.BorderLayout;
012    import java.awt.event.ActionEvent;
013    import java.awt.event.ActionListener;
014    import java.util.ArrayList;
015    import java.util.Collection;
016    import java.util.HashMap;
017    import java.util.Iterator;
018    import java.util.Vector;
019    
020    import javax.swing.Box;
021    import javax.swing.BoxLayout;
022    import javax.swing.ButtonGroup;
023    import javax.swing.JButton;
024    import javax.swing.JCheckBox;
025    import javax.swing.JDialog;
026    import javax.swing.JOptionPane;
027    import javax.swing.JPanel;
028    import javax.swing.JRadioButton;
029    import javax.swing.JScrollPane;
030    import javax.swing.JTable;
031    import javax.swing.event.TableModelEvent;
032    import javax.swing.table.TableModel;
033    
034    import jcolibri.cbrcore.Attribute;
035    import jcolibri.cbrcore.CBRCase;
036    import jcolibri.cbrcore.CaseComponent;
037    import jcolibri.extensions.recommendation.casesDisplay.UserChoice;
038    import jcolibri.extensions.recommendation.casesDisplay.utils.RadioButtonEditor;
039    import jcolibri.extensions.recommendation.casesDisplay.utils.RadioButtonTableRenderer;
040    import jcolibri.extensions.recommendation.navigationByAsking.ObtainQueryWithAttributeQuestionMethod;
041    import jcolibri.method.retrieve.FilterBasedRetrieval.predicates.Equal;
042    import jcolibri.method.retrieve.FilterBasedRetrieval.predicates.FilterPredicate;
043    import jcolibri.util.AttributeUtils;
044    
045    /**
046     * This method shows the cases in a table and also allows to show buttons with 
047     * critiques.
048     * <br>
049     * It is an extension of jcolibri.extensions.recommendation.casesDisplay.DisplayCasesTableMethod used
050     * in navigationByProposing recommenders.
051     * <br>
052     * This method enables and disables the critiques buttons depending on the values of the 
053     * available cases. (For example, it has no sense to show a "creaper" button if there are
054     * not cheaper cases).
055     * Usually, displayed cases are the same than working cases, but when using diversity 
056     * algorithms only three of the working cases are displayed.
057     * 
058     * @author Juan A. Recio-Garcia
059     * @author Developed at University College Cork (Ireland) in collaboration with Derek Bridge.
060     * @version 1.0
061     *
062     * @see jcolibri.extensions.recommendation.navigationByProposing.CriticalUserChoice
063     * @see jcolibri.extensions.recommendation.casesDisplay.DisplayCasesTableMethod
064     */
065    public class DisplayCasesTableWithCritiquesMethod
066    {
067        private static JDialog dialog;
068        
069        private static ButtonGroup group;
070        private static int returnCode = UserChoice.QUIT;
071    
072        private static Collection<CBRCase> displayedCases;
073        private static Collection<CritiqueOption> displayedCritiques;
074        private static Collection<CBRCase> _availableCases;
075        
076        private static Collection<CritiqueOption> userCritiques;
077        private static JTable table;
078        
079        private static HashMap<CritiqueOption,JButton> critiquesMap;
080        
081        private static CBRCase critiquedQuery;
082        
083        
084        /**
085         * This method shows the cases in a table and also allows to show buttons with 
086         * critiques.
087         * @param cases to be shown
088         * @param critiques to the cases (buttons are automatically generated from these critiques).
089         * @param availableCases are the current working cases. Critiques are enabled depending on these cases.
090         * @return a CriticalUserChoice object.
091         */
092        public static CriticalUserChoice displayCasesInTableWithCritiques(Collection<CBRCase> cases, Collection<CritiqueOption> critiques, Collection<CBRCase> availableCases )
093        {
094            displayedCases = cases;
095            displayedCritiques = critiques;
096            _availableCases = availableCases;
097            critiquesMap = new HashMap<CritiqueOption, JButton>();
098            
099            dialog = new JDialog();
100            dialog.setTitle(cases.size()+" Retrieved cases");
101            dialog.setModal(true);
102            
103    
104            userCritiques = new ArrayList<CritiqueOption>();
105            
106            Vector<Object> columnNames = extractColumnNames(cases.iterator().next());
107            
108    
109            Vector<Object> rows = new Vector<Object>();
110            for(CBRCase c: cases)
111                rows.add(getAttributes(c));
112            
113            table = new JTable(rows, columnNames){
114    
115                private static final long serialVersionUID = 1L;
116    
117                public void tableChanged(TableModelEvent e) {
118                            super.tableChanged(e);
119                            repaint();
120                  }
121            };
122            
123            table.getColumn("Select").setCellRenderer(
124                    new RadioButtonTableRenderer());
125            table.getColumn("Select").setCellEditor(
126                    new RadioButtonEditor(new JCheckBox()));
127            
128            group = new ButtonGroup();
129            TableModel tm = table.getModel();
130            for(int i=0; i<tm.getRowCount();i++)
131            {
132                JRadioButton rb = (JRadioButton) tm.getValueAt(i, 0);
133                group.add(rb);
134                DisplayCasesTableWithCritiquesMethod any = new DisplayCasesTableWithCritiquesMethod();
135                rb.addActionListener(any.new ItemRadioButtonListener(i));
136            }
137            
138            
139            JScrollPane scrollPane = new JScrollPane(table);
140            table.setFillsViewportHeight(true);
141            
142            JPanel mainPanel = new JPanel();
143            mainPanel.setLayout(new BorderLayout());
144            mainPanel.add(scrollPane,BorderLayout.CENTER);
145            
146            
147            JPanel actionsPanel = new JPanel();
148            actionsPanel.setLayout(new BoxLayout(actionsPanel,BoxLayout.X_AXIS));
149            
150            JButton ok = new JButton("Add to Basket");
151            ok.addActionListener(new ActionListener(){
152                public void actionPerformed(ActionEvent arg0)
153                {
154                    if(table.getSelectedRow() == -1)
155                        JOptionPane.showMessageDialog(dialog, "You should choose one item", "Error", JOptionPane.ERROR_MESSAGE);
156                    else
157                    {
158                        returnCode = UserChoice.BUY;
159                        Iterator<CBRCase> iter = displayedCases.iterator();
160                        CBRCase _case = iter.next();
161                        for(int i=0; i<table.getSelectedRow(); i++)
162                             _case = iter.next();
163                        critiquedQuery = _case;
164                        dialog.setVisible(false);
165                    }
166                } 
167            });
168            JButton quit = new JButton("Quit");
169            quit.addActionListener(new ActionListener(){
170                public void actionPerformed(ActionEvent arg0)
171                {
172                    returnCode = UserChoice.QUIT;
173                    dialog.setVisible(false);
174                } 
175            });
176    
177            actionsPanel.add(Box.createHorizontalGlue());
178            actionsPanel.add(ok);
179            actionsPanel.add(quit);
180            actionsPanel.add(Box.createHorizontalGlue());
181            
182            JPanel critiquesPanel = new JPanel();
183            critiquesPanel.setLayout(new BoxLayout(critiquesPanel,BoxLayout.X_AXIS));
184            critiquesPanel.add(Box.createHorizontalGlue());
185            for(CritiqueOption critique: critiques)
186            {
187                JButton b = new JButton(critique.getLabel());
188                DisplayCasesTableWithCritiquesMethod any = new DisplayCasesTableWithCritiquesMethod();
189                b.addActionListener(any.new CritiqueButtonAction(critique));
190                critiquesMap.put(critique, b);
191                critiquesPanel.add(b);
192                critiquesPanel.add(Box.createHorizontalGlue());
193            }
194            critiquesPanel.setBorder(javax.swing.BorderFactory.createTitledBorder("Critiques"));
195            
196            
197            JPanel south = new JPanel();
198            south.setLayout(new BoxLayout(south, BoxLayout.Y_AXIS));
199            south.add(actionsPanel);
200            south.add(critiquesPanel);
201            mainPanel.add(south, BorderLayout.SOUTH);
202            
203            dialog.getContentPane().add(mainPanel);
204            dialog.setSize(800, 600);
205            jcolibri.method.gui.utils.WindowUtils.centerWindow(dialog);
206            
207            System.out.println("Available cases:");
208            for(CBRCase c: _availableCases)
209                System.out.println(c);
210            
211            dialog.setVisible(true);
212    
213            
214            return new CriticalUserChoice(returnCode, userCritiques, critiquedQuery);
215        }
216        
217        /**
218         * Disable critiques buttons depending on the selected case (row)
219         * @param row of the table
220         */
221        private static void disableCritiques(int row)
222        {
223            Iterator<CBRCase> iter = displayedCases.iterator();
224            CBRCase _case = iter.next();
225            for(int i=0; i<row; i++)
226               _case = iter.next();
227            
228            for(CritiqueOption co : displayedCritiques)
229            {
230                FilterPredicate fp = co.getPredicate();
231                Attribute a = co.getAttribute();
232                Object valueSelected = AttributeUtils.findValue(a, _case.getDescription());
233                boolean therearemore = false;
234                for(CBRCase cbCase : _availableCases)
235                {
236                    Object valueOther    = AttributeUtils.findValue(a, cbCase);
237                    try
238                    {
239                        boolean res = fp.compute(valueOther,valueSelected);
240                        if(fp instanceof Equal)
241                            res = !res;
242                        if(res)
243                        {
244                            therearemore = true;
245                            break;
246                        }
247                    } catch (Exception e)
248                    {
249                        org.apache.commons.logging.LogFactory.getLog(DisplayCasesTableWithCritiquesMethod.class).error(e);
250                        
251                    }
252                }
253                critiquesMap.get(co).setEnabled(therearemore);
254            }
255            
256        }
257        
258        /**
259         * Returns the attributes of a case
260         */
261        private static Vector getAttributes(CBRCase c)
262        {
263            Vector<Object> res = new Vector<Object>();
264            
265            JRadioButton rb = new JRadioButton(c.getID().toString());
266            res.add(rb);
267            
268            getAttributes(c.getDescription(), res);
269            getAttributes(c.getSolution(), res);
270            getAttributes(c.getJustificationOfSolution(), res);
271            getAttributes(c.getResult(), res);
272            
273            return res;
274        }
275        
276        /**
277         * Returns the attributes of a CaseComponent
278         */
279        private static void getAttributes(CaseComponent cc, Vector<Object> res)
280        {
281            Collection<Attribute> atts = AttributeUtils.getAttributes(cc);
282            if(atts == null)
283                return;
284    
285            Attribute id = cc.getIdAttribute();
286            for(Attribute a: atts)
287            {
288                if(!a.equals(id))
289                    res.add(AttributeUtils.findValue(a, cc));
290            }
291        }
292        
293        /**
294         * Gets the column names from the names of the attributes of a case
295         */
296        private static Vector<Object> extractColumnNames(CBRCase c)
297        {
298            Vector<Object> res = new Vector<Object>();
299            res.add("Select");
300            extractColumnNames(c.getDescription(),res);
301            extractColumnNames(c.getSolution(),res);
302            extractColumnNames(c.getJustificationOfSolution(),res);
303            extractColumnNames(c.getResult(),res);
304            return res;
305        }
306        
307        /**
308         * Returns the names of the attributes of a CaseComponent.
309         */
310        private static void extractColumnNames(CaseComponent cc, Vector<Object> res)
311        {
312            Collection<Attribute> atts = AttributeUtils.getAttributes(cc);
313            if(atts == null)
314                return;
315            Attribute id = cc.getIdAttribute();
316            for(Attribute a: atts)
317            {
318                if(!a.equals(id))
319                    res.add(a.getName());
320            }
321        }
322        
323        /**
324         * Listener for the RadioButtons
325         * @author Juan A. Recio-Garcia
326         * @version 1.0
327         */
328        private class ItemRadioButtonListener implements ActionListener
329        {
330            int row = 0;
331            public ItemRadioButtonListener(int row)
332            {
333                this.row = row;
334            }
335            public void actionPerformed(ActionEvent arg0)
336            {
337               JRadioButton rb = (JRadioButton)arg0.getSource();
338               if(rb.isSelected())
339                   disableCritiques(row);
340            }
341            
342        }
343        
344        /**
345         * Listener for the critiques buttons.
346         * @author Juan A. Recio-Garcia
347         * @version 1.0
348         *
349         */
350        private class CritiqueButtonAction implements ActionListener
351        {
352            CritiqueOption critique;
353            public CritiqueButtonAction(CritiqueOption co)
354            {
355                critique = co;
356            }
357            public void actionPerformed(ActionEvent arg0)
358            {
359                if(table.getSelectedRowCount()<=0)
360                {
361                    JOptionPane.showMessageDialog(dialog, "You should choose one item", "Error", JOptionPane.ERROR_MESSAGE);
362                    return;
363                }
364    
365                
366                Iterator<CBRCase> iter = displayedCases.iterator();
367                CBRCase _case = iter.next();
368                for(int i=0; i<table.getSelectedRow(); i++)
369                     _case = iter.next();
370                
371                critiquedQuery = _case;
372                returnCode = UserChoice.REFINE_QUERY;
373            
374                if(critique.getPredicate().getClass().equals(Equal.class))
375                {
376                    ObtainQueryWithAttributeQuestionMethod.obtainQueryWithAttributeQuestion(critiquedQuery, critique.getAttribute(), new HashMap<Attribute,String>(), _availableCases);
377                }
378                userCritiques.add(critique);
379                dialog.setVisible(false);
380    
381            }
382            
383        }
384        
385    }