1/*
2 [The "BSD licence"]
3 Copyright (c) 2009 Shaoting Cai
4 All rights reserved.
5
6 Redistribution and use in source and binary forms, with or without
7 modification, are permitted provided that the following conditions
8 are met:
9 1. Redistributions of source code must retain the above copyright
10    notice, this list of conditions and the following disclaimer.
11 2. Redistributions in binary form must reproduce the above copyright
12    notice, this list of conditions and the following disclaimer in the
13    documentation and/or other materials provided with the distribution.
14 3. The name of the author may not be used to endorse or promote products
15    derived from this software without specific prior written permission.
16
17 THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
18 IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
19 OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
20 IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
21 INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
22 NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23 DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24 THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26 THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27*/
28
29package org.antlr.gunit.swingui;
30
31import org.antlr.gunit.swingui.model.*;
32import org.antlr.gunit.swingui.ImageFactory;
33import java.awt.*;
34import java.awt.event.*;
35import java.util.HashMap;
36import javax.swing.*;
37import javax.swing.event.*;
38
39/**
40 *
41 * @author scai
42 */
43public class TestCaseEditController implements IController {
44
45    private JPanel view = new JPanel();
46
47    private JScrollPane scroll;
48    private JPanel paneDetail;
49    private AbstractEditorPane paneDetailInput, paneDetailOutput;
50    private JToolBar toolbar;
51    private JList listCases;
52    private ListModel listModel ;
53
54    public ActionListener onTestCaseNumberChange;
55
56    /* EDITORS */
57    private InputFileEditor editInputFile;
58    private InputStringEditor editInputString;
59    private InputMultiEditor editInputMulti;
60    private OutputResultEditor editOutputResult;
61    private OutputAstEditor editOutputAST;
62    private OutputStdEditor editOutputStd;
63    private OutputReturnEditor editOutputReturn;
64
65    private JComboBox comboInputType, comboOutputType;
66
67    /* TYPE NAME */
68    private static final String IN_TYPE_STRING = "Single-line Text";
69    private static final String IN_TYPE_MULTI = "Multi-line Text";
70    private static final String IN_TYPE_FILE = "Disk File";
71    private static final String OUT_TYPE_BOOL = "OK or Fail";
72    private static final String OUT_TYPE_AST = "AST";
73    private static final String OUT_TYPE_STD = "Standard Output";
74    private static final String OUT_TYPE_RET = "Return Value";
75
76    private static final String DEFAULT_IN_SCRIPT = "";
77    private static final String DEFAULT_OUT_SCRIPT = "";
78
79    private static final Object[] INPUT_TYPE =  {
80        IN_TYPE_STRING, IN_TYPE_MULTI, IN_TYPE_FILE
81    };
82
83    private static final Object[] OUTPUT_TYPE = {
84        OUT_TYPE_BOOL, OUT_TYPE_AST, OUT_TYPE_STD, OUT_TYPE_RET
85    };
86
87    /* SIZE */
88    private static final int TEST_CASE_DETAIL_WIDTH = 300;
89    private static final int TEST_EDITOR_WIDTH = 280;
90    private static final int TEST_CASE_DETAIL_HEIGHT = 250;
91    private static final int TEST_EDITOR_HEIGHT = 120;
92
93    /* MODEL */
94    private Rule currentRule = null;
95    private TestCase currentTestCase = null;
96
97    /* END OF MODEL*/
98
99    private static final HashMap<Class, String> TypeNameTable;
100    static {
101        TypeNameTable = new HashMap<Class, String> ();
102        TypeNameTable.put(TestCaseInputString.class, IN_TYPE_STRING);
103        TypeNameTable.put(TestCaseInputMultiString.class, IN_TYPE_MULTI);
104        TypeNameTable.put(TestCaseInputFile.class, IN_TYPE_FILE);
105
106        TypeNameTable.put(TestCaseOutputResult.class, OUT_TYPE_BOOL);
107        TypeNameTable.put(TestCaseOutputAST.class, OUT_TYPE_AST);
108        TypeNameTable.put(TestCaseOutputStdOut.class, OUT_TYPE_STD);
109        TypeNameTable.put(TestCaseOutputReturn.class, OUT_TYPE_RET);
110    }
111
112    //private WorkSpaceView owner;
113
114    public TestCaseEditController(WorkSpaceView workspace) {
115        //this.owner = workspace;
116        initComponents();
117    }
118
119    public TestCaseEditController() {
120        initComponents();
121    }
122
123    public void OnLoadRule(Rule rule) {
124        if(rule == null) throw new IllegalArgumentException("Null");
125        this.currentRule = rule;
126        this.currentTestCase = null;
127        this.listModel = rule;
128        this.listCases.setModel(this.listModel);
129    }
130
131    public void setCurrentTestCase(TestCase testCase) {
132        if(testCase == null) throw new IllegalArgumentException("Null");
133        this.listCases.setSelectedValue(testCase, true);
134        this.currentTestCase = testCase;
135    }
136
137    public Rule getCurrentRule() {
138        return this.currentRule;
139    }
140
141    private void initComponents() {
142
143        /* CASE LIST */
144        listCases = new JList();
145        listCases.addListSelectionListener(new TestCaseListSelectionListener());
146        listCases.setCellRenderer(listRenderer);
147        listCases.setOpaque(false);
148
149        scroll = new JScrollPane(listCases);
150        scroll.setBorder(BorderFactory.createTitledBorder(
151                BorderFactory.createEmptyBorder(), "Test Cases"));
152        scroll.setOpaque(false);
153        scroll.setViewportBorder(BorderFactory.createEtchedBorder());
154
155        /* CASE DETAIL */
156
157        editInputString = new InputStringEditor();
158        editInputMulti = new InputMultiEditor();
159        editInputFile = new InputFileEditor();
160
161        editOutputResult = new OutputResultEditor();
162        editOutputAST = new OutputAstEditor();
163        editOutputStd = new OutputStdEditor();
164        editOutputReturn = new OutputReturnEditor();
165
166        paneDetail = new JPanel();
167        paneDetail.setBorder(BorderFactory.createEmptyBorder());
168        paneDetail.setOpaque(false);
169
170        comboInputType = new JComboBox(INPUT_TYPE);
171        comboInputType.addActionListener(new ActionListener() {
172            public void actionPerformed(ActionEvent event) {
173                OnInputTestCaseTypeChanged(comboInputType.getSelectedItem());
174            }
175        });
176        comboOutputType = new JComboBox(OUTPUT_TYPE);
177        comboOutputType.addActionListener(new ActionListener() {
178            public void actionPerformed(ActionEvent event) {
179                OnOutputTestCaseTypeChanged(comboOutputType.getSelectedItem());
180            }
181        });
182        paneDetailInput = new InputEditorPane(comboInputType);
183        paneDetailOutput = new OutputEditorPane(comboOutputType);
184
185        BoxLayout layout = new BoxLayout(paneDetail, BoxLayout.PAGE_AXIS);
186        paneDetail.setLayout(layout);
187
188        paneDetail.add(this.paneDetailInput);
189        paneDetail.add(this.paneDetailOutput);
190
191        /* TOOLBAR */
192        toolbar = new JToolBar("Edit TestCases", JToolBar.VERTICAL);
193        toolbar.setFloatable(false);
194        toolbar.add(new AddTestCaseAction());
195        toolbar.add(new RemoveTestCaseAction());
196
197        /* COMPOSITE */
198        view.setLayout(new BorderLayout());
199        view.setBorder(BorderFactory.createEmptyBorder());
200        view.setOpaque(false);
201        view.add(toolbar, BorderLayout.WEST);
202        view.add(scroll, BorderLayout.CENTER);
203        view.add(paneDetail, BorderLayout.EAST);
204    }
205
206    private void updateInputEditor() {
207        JComponent editor = null;
208
209        if(currentTestCase != null ) {
210            ITestCaseInput input = this.currentTestCase.getInput();
211            if(input instanceof TestCaseInputString) {
212                this.editInputString.setText(input.getScript());
213                editor = this.editInputString;
214                comboInputType.setSelectedItem(IN_TYPE_STRING);
215            } else if(input instanceof TestCaseInputMultiString) {
216                this.editInputMulti.setText(input.getScript());
217                editor = this.editInputMulti.getView();
218                comboInputType.setSelectedItem(IN_TYPE_MULTI);
219            } else if(input instanceof TestCaseInputFile) {
220                this.editInputFile.setText(input.getScript());
221                editor = this.editInputFile;
222                comboInputType.setSelectedItem(IN_TYPE_FILE);
223            } else {
224                throw new Error("Wrong type");
225            }
226        }
227
228        paneDetailInput.setEditor(editor);
229    }
230
231    private void updateOutputEditor() {
232        JComponent editor = null;
233
234        if(currentTestCase != null) {
235
236            ITestCaseOutput output = this.currentTestCase.getOutput();
237
238            if(output instanceof TestCaseOutputAST) {
239
240                this.editOutputAST.setText(output.getScript());
241                editor = this.editOutputAST.getView();
242                comboOutputType.setSelectedItem(OUT_TYPE_AST);
243
244            } else if(output instanceof TestCaseOutputResult) {
245
246                this.editOutputResult.setValue(output.getScript());
247                editor = this.editOutputResult;
248                comboOutputType.setSelectedItem(OUT_TYPE_BOOL);
249
250            } else if(output instanceof TestCaseOutputStdOut) {
251
252                this.editOutputStd.setText(output.getScript());
253                editor = this.editOutputStd.getView();
254                comboOutputType.setSelectedItem(OUT_TYPE_STD);
255
256            } else if(output instanceof TestCaseOutputReturn) {
257
258                this.editOutputReturn.setText(output.getScript());
259                editor = this.editOutputReturn.getView();
260                comboOutputType.setSelectedItem(OUT_TYPE_RET);
261
262            } else {
263
264                throw new Error("Wrong type");
265
266            }
267
268        }
269        this.paneDetailOutput.setEditor(editor);
270    }
271
272    private void OnInputTestCaseTypeChanged(Object inputTypeStr) {
273        if(this.currentTestCase != null) {
274            ITestCaseInput input ;
275            if(inputTypeStr == IN_TYPE_STRING) {
276                input = new TestCaseInputString(DEFAULT_IN_SCRIPT);
277            } else if(inputTypeStr == IN_TYPE_MULTI) {
278                input = new TestCaseInputMultiString(DEFAULT_IN_SCRIPT);
279            } else if(inputTypeStr == IN_TYPE_FILE) {
280                input = new TestCaseInputFile(DEFAULT_IN_SCRIPT);
281            } else {
282                throw new Error("Wrong Type");
283            }
284
285            if(input.getClass().equals(this.currentTestCase.getInput().getClass()))
286                return ;
287
288            this.currentTestCase.setInput(input);
289        }
290        this.updateInputEditor();
291    }
292
293    private void OnOutputTestCaseTypeChanged(Object outputTypeStr) {
294        if(this.currentTestCase != null) {
295
296            ITestCaseOutput output ;
297            if(outputTypeStr == OUT_TYPE_AST) {
298                output = new TestCaseOutputAST(DEFAULT_OUT_SCRIPT);
299            } else if(outputTypeStr == OUT_TYPE_BOOL) {
300                output = new TestCaseOutputResult(false);
301            } else if(outputTypeStr == OUT_TYPE_STD) {
302                output = new TestCaseOutputStdOut(DEFAULT_OUT_SCRIPT);
303            } else if(outputTypeStr == OUT_TYPE_RET) {
304                output = new TestCaseOutputReturn(DEFAULT_OUT_SCRIPT);
305            } else {
306                throw new Error("Wrong Type");
307            }
308
309            if(output.getClass().equals(this.currentTestCase.getOutput().getClass()))
310                return ;
311
312            this.currentTestCase.setOutput(output);
313        }
314        this.updateOutputEditor();
315    }
316
317
318    private void OnTestCaseSelected(TestCase testCase) {
319        //if(testCase == null) throw new RuntimeException("Null TestCase");
320        this.currentTestCase = testCase;
321        updateInputEditor();
322        updateOutputEditor();
323
324    }
325
326    private void OnAddTestCase() {
327        if(currentRule == null) return;
328
329        final TestCase newCase = new TestCase(
330                new TestCaseInputString(""),
331                new TestCaseOutputResult(true));
332        this.currentRule.addTestCase(newCase);
333        setCurrentTestCase(newCase);
334
335        this.listCases.setSelectedValue(newCase, true);
336        this.listCases.updateUI();
337        this.OnTestCaseSelected(newCase);
338        this.onTestCaseNumberChange.actionPerformed(null);
339    }
340
341    private void OnRemoveTestCase() {
342        if(currentTestCase == null) return;
343        currentRule.removeElement(currentTestCase);
344        listCases.updateUI();
345
346        final TestCase nextActiveCase = listCases.isSelectionEmpty() ?
347            null : (TestCase) listCases.getSelectedValue() ;
348        OnTestCaseSelected(nextActiveCase);
349        this.onTestCaseNumberChange.actionPerformed(null);
350    }
351
352    public Object getModel() {
353        return currentRule;
354    }
355
356    public Component getView() {
357        return view;
358    }
359
360    /* EDITOR CONTAINER */
361
362    abstract public class AbstractEditorPane extends JPanel {
363
364        private JComboBox combo;
365        private JComponent editor;
366        private String title;
367        private JLabel placeHolder = new JLabel();
368
369        public AbstractEditorPane(JComboBox comboBox, String title) {
370            this.combo = comboBox;
371            this.editor = placeHolder;
372            this.title = title;
373            this.initComponents();
374        }
375
376        private void initComponents() {
377            placeHolder.setPreferredSize(new Dimension(
378                    TEST_CASE_DETAIL_WIDTH, TEST_CASE_DETAIL_HEIGHT));
379            this.setLayout(new BoxLayout(this, BoxLayout.Y_AXIS));
380            this.add(combo, BorderLayout.NORTH);
381            this.add(editor, BorderLayout.CENTER);
382            this.setOpaque(false);
383            this.setBorder(BorderFactory.createTitledBorder(title));
384            this.setPreferredSize(new Dimension(
385                    TEST_CASE_DETAIL_WIDTH, TEST_CASE_DETAIL_HEIGHT));
386        }
387
388        public void setEditor(JComponent newEditor) {
389            if(newEditor == null) newEditor = placeHolder;
390            this.remove(editor);
391            this.add(newEditor);
392            this.editor = newEditor;
393            this.updateUI();
394        }
395    }
396
397    public class InputEditorPane extends AbstractEditorPane {
398        public InputEditorPane(JComboBox comboBox) {
399            super(comboBox, "Input");
400        }
401    }
402
403    public class OutputEditorPane extends AbstractEditorPane {
404        public OutputEditorPane(JComboBox comboBox) {
405            super(comboBox, "Output");
406        }
407    }
408
409    /* INPUT EDITORS */
410
411    public class InputStringEditor extends JTextField implements CaretListener {
412        public InputStringEditor() {
413            super();
414
415            this.setBorder(BorderFactory.createLineBorder(Color.LIGHT_GRAY));
416            this.addCaretListener(this);
417        }
418
419        public void caretUpdate(CaretEvent arg0) {
420            currentTestCase.getInput().setScript(getText());
421            listCases.updateUI();
422        }
423    }
424
425    public class InputMultiEditor implements CaretListener {
426        private JTextArea textArea = new JTextArea(20, 30);
427        private JScrollPane scroll = new JScrollPane(textArea,
428                JScrollPane.VERTICAL_SCROLLBAR_ALWAYS,
429                JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS);
430
431        public InputMultiEditor() {
432            super();
433            scroll.setBorder(BorderFactory.createLineBorder(Color.LIGHT_GRAY));
434            textArea.addCaretListener(this);
435        }
436
437        public void caretUpdate(CaretEvent arg0) {
438            currentTestCase.getInput().setScript(getText());
439            listCases.updateUI();
440        }
441
442        public String getText() {
443            return textArea.getText();
444        }
445
446        public void setText(String text) {
447            textArea.setText(text);
448        }
449
450        public JComponent getView() {
451            return scroll;
452        }
453    }
454
455    public class InputFileEditor extends InputStringEditor {};
456
457    public class OutputResultEditor extends JPanel implements ActionListener {
458
459        private JToggleButton tbFail, tbOk;
460
461        public OutputResultEditor() {
462            super();
463
464            tbFail = new JToggleButton("Fail");
465            tbOk = new JToggleButton("OK");
466            ButtonGroup group = new ButtonGroup();
467            group.add(tbFail);
468            group.add(tbOk);
469
470            this.add(tbFail);
471            this.add(tbOk);
472
473            this.tbFail.addActionListener(this);
474            this.tbOk.addActionListener(this);
475
476            this.setPreferredSize(
477                    new Dimension(TEST_EDITOR_WIDTH, 100));
478        }
479
480        public void actionPerformed(ActionEvent e) {
481            TestCaseOutputResult output =
482                    (TestCaseOutputResult) currentTestCase.getOutput();
483
484            if(e.getSource() == tbFail) {
485                output.setScript(false);
486            } else {
487                output.setScript(true);
488            }
489
490            listCases.updateUI();
491        }
492
493        public void setValue(String value) {
494            if(TestCaseOutputResult.OK.equals(value)) {
495                this.tbOk.setSelected(true);
496            } else {
497                this.tbFail.setSelected(true);
498            }
499        }
500    }
501
502
503    public class OutputAstEditor implements CaretListener {
504        private JTextArea textArea = new JTextArea(20, 30);
505        private JScrollPane scroll = new JScrollPane(textArea,
506                JScrollPane.VERTICAL_SCROLLBAR_ALWAYS,
507                JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS);
508
509        public OutputAstEditor() {
510            super();
511            scroll.setBorder(BorderFactory.createLineBorder(Color.LIGHT_GRAY));
512            textArea.addCaretListener(this);
513        }
514
515        public void caretUpdate(CaretEvent arg0) {
516            currentTestCase.getOutput().setScript(getText());
517            listCases.updateUI();
518        }
519
520        public void setText(String text) {
521            this.textArea.setText(text);
522        }
523
524        public String getText() {
525            return this.textArea.getText();
526        }
527
528        public JScrollPane getView() {
529            return this.scroll;
530        }
531    }
532
533
534    public class OutputStdEditor extends OutputAstEditor {}
535    public class OutputReturnEditor extends OutputAstEditor {}
536
537    /* EVENT HANDLERS */
538
539    private class TestCaseListSelectionListener implements ListSelectionListener {
540
541        public void valueChanged(ListSelectionEvent e) {
542
543            if(e.getValueIsAdjusting()) return;
544            final JList list = (JList) e.getSource();
545            final TestCase value = (TestCase) list.getSelectedValue();
546            if(value != null) OnTestCaseSelected(value);
547
548        }
549
550    }
551
552    /* ACTIONS */
553
554    private class AddTestCaseAction extends AbstractAction {
555        public AddTestCaseAction() {
556            super("Add", ImageFactory.getSingleton().ADD);
557            putValue(SHORT_DESCRIPTION, "Add a gUnit test case.");
558        }
559        public void actionPerformed(ActionEvent e) {
560            OnAddTestCase();
561        }
562    }
563
564    private class RemoveTestCaseAction extends AbstractAction {
565        public RemoveTestCaseAction() {
566            super("Remove", ImageFactory.getSingleton().DELETE);
567            putValue(SHORT_DESCRIPTION, "Remove a gUnit test case.");
568        }
569        public void actionPerformed(ActionEvent e) {
570            OnRemoveTestCase();
571        }
572    }
573
574    /* CELL RENDERERS */
575
576    private static TestCaseListRenderer listRenderer
577            = new TestCaseListRenderer();
578
579    private static class TestCaseListRenderer implements ListCellRenderer {
580
581        private static Font IN_FONT = new Font("mono", Font.PLAIN, 12);
582        private static Font OUT_FONT = new Font("default", Font.BOLD, 12);
583
584        public static String clamp(String text, int len) {
585            if(text.length() > len) {
586                return text.substring(0, len - 3).concat("...");
587            } else {
588                return text;
589            }
590        }
591
592        public static String clampAtNewLine(String text) {
593            int pos = text.indexOf('\n');
594            if(pos >= 0) {
595                return text.substring(0, pos).concat("...");
596            } else {
597                return text;
598            }
599        }
600
601        public Component getListCellRendererComponent(
602                JList list, Object value, int index,
603                boolean isSelected, boolean hasFocus) {
604
605            final JPanel pane = new JPanel();
606
607            if (value instanceof TestCase) {
608                final TestCase item = (TestCase) value;
609
610                // create components
611                final JLabel labIn = new JLabel(
612                        clamp(clampAtNewLine(item.getInput().getScript()), 18));
613                final JLabel labOut = new JLabel(
614                        clamp(clampAtNewLine(item.getOutput().getScript()), 18));
615                labOut.setFont(OUT_FONT);
616                labIn.setFont(IN_FONT);
617
618                labIn.setIcon(item.getInput() instanceof TestCaseInputFile ?
619                    ImageFactory.getSingleton().FILE16 :
620                    ImageFactory.getSingleton().EDIT16);
621
622                pane.setBorder(BorderFactory.createEtchedBorder());
623                pane.setLayout(new BoxLayout(pane, BoxLayout.Y_AXIS));
624                pane.add(labIn);
625                pane.add(labOut);
626                pane.setBackground(isSelected ? Color.LIGHT_GRAY : Color.WHITE);
627            }
628
629            return pane;
630        }
631    }
632
633}
634