001package com.ganteater.ae.desktop.editor;
002
003import java.awt.Color;
004import java.awt.FileDialog;
005import java.awt.Font;
006import java.awt.Point;
007import java.awt.event.ActionEvent;
008import java.awt.event.ActionListener;
009import java.awt.event.KeyAdapter;
010import java.awt.event.KeyEvent;
011import java.awt.event.MouseEvent;
012import java.awt.event.MouseListener;
013import java.lang.reflect.Method;
014import java.util.ArrayList;
015import java.util.Arrays;
016import java.util.LinkedHashMap;
017import java.util.List;
018import java.util.Map;
019import java.util.Properties;
020import java.util.Set;
021import java.util.StringTokenizer;
022
023import javax.swing.AbstractAction;
024import javax.swing.JMenu;
025import javax.swing.JMenuItem;
026import javax.swing.JOptionPane;
027import javax.swing.JPopupMenu;
028import javax.swing.JRootPane;
029import javax.swing.JToolTip;
030import javax.swing.SwingUtilities;
031
032import org.apache.commons.lang.ClassUtils;
033import org.apache.commons.lang.StringUtils;
034
035import com.ganteater.ae.AEWorkspace;
036import com.ganteater.ae.ILogger;
037import com.ganteater.ae.OperationHolder;
038import com.ganteater.ae.TaskCancelingException;
039import com.ganteater.ae.desktop.ui.DialogPopupMenu;
040import com.ganteater.ae.desktop.ui.OptionPane;
041import com.ganteater.ae.processor.BaseProcessor;
042import com.ganteater.ae.processor.Processor;
043import com.ganteater.ae.processor.annotation.CommandExamples;
044import com.ganteater.ae.processor.annotation.CommandHotHepl;
045import com.ganteater.ae.util.xml.easyparser.EasyParser;
046import com.ganteater.ae.util.xml.easyparser.Node;
047
048public class CodeHelper extends KeyAdapter implements MouseListener {
049
050        private static final String RUN_COMMAND_METHODE_PREFIX = "runCommand";
051        private static final String XML_PARSIONG_FAILED_MESSAGE = "XML parsiong failed.";
052
053        public static final Font POPUP_MENU_FONT = new Font("Arial", Font.PLAIN, 9);
054
055        private AeEditPanel editor;
056        private ILogger log;
057
058        public CodeHelper(AeEditPanel editor, ILogger log) {
059                this.editor = editor;
060                this.log = log;
061        }
062
063        @Override
064        public void keyPressed(KeyEvent e) {
065
066                if (e.getKeyCode() == KeyEvent.VK_D && e.isControlDown()) {
067                        editor.getEditor().removeLine();
068                } else if (e.getKeyCode() == KeyEvent.VK_F9) {
069                        editor.runTask();
070                } else if (e.getKeyCode() == KeyEvent.VK_F && e.isControlDown()) {
071                } else if (e.getKeyCode() == KeyEvent.VK_SPACE && e.isControlDown()) {
072                        showCommandsMenu();
073                }
074        }
075
076        private void showCommandsMenu() {
077                String text = editor.getText();
078                int curpos = editor.getCaretPosition();
079
080                Processor makeProcessor = editor.getTaskProcessor();
081                String substring = StringUtils.substring(text, 0, curpos);
082                int startExternTag = StringUtils.lastIndexOf(substring, "<Extern");
083                int closeExternTag = StringUtils.lastIndexOf(substring, "</Extern");
084                if (closeExternTag < 0 || closeExternTag < startExternTag) {
085                        int endExternTag = StringUtils.indexOf(substring, '>', startExternTag);
086                        String externTag = StringUtils.substring(substring, startExternTag, endExternTag);
087                        if (StringUtils.isNotBlank(externTag)) {
088                                externTag = externTag + "/>";
089                                try {
090                                        Node externNode = new EasyParser().getObject(externTag);
091                                        makeProcessor = makeProcessor.makeProcessor(externNode);
092
093                                } catch (Exception e1) {
094                                        log.error(XML_PARSIONG_FAILED_MESSAGE, e1);
095                                }
096                        }
097                }
098
099                String theStartCommand = null;
100                String theStartMacroIns = null;
101
102                for (int i = curpos - 1; i >= 0; i--) {
103                        char charAt = text.charAt(i);
104                        if (charAt == ' ') {
105                                break;
106
107                        } else if (charAt == '<') {
108                                theStartCommand = text.substring(i + 1, curpos);
109
110                                break;
111                        } else if (charAt == '$') {
112                                theStartMacroIns = text.substring(i + 1, curpos);
113                                break;
114                        } else {
115                                theStartCommand = "";
116                        }
117                }
118
119                DialogPopupMenu menu = new DialogPopupMenu(editor.getEditor());
120                menu.setAutoscrolls(true);
121
122                if (theStartCommand != null) {
123                        showCommandPopupMenu(theStartCommand, menu, makeProcessor);
124
125                }
126                if (theStartMacroIns != null) {
127                        showMacroPopupMenu(theStartMacroIns, menu);
128                } else {
129                        menu = editor.contextHelp(menu);
130                }
131                Point magicCaretPosition = editor.getMagicCaretPosition();
132                if (magicCaretPosition == null)
133                        magicCaretPosition = new Point();
134                menu.show(editor.getEditor(), magicCaretPosition.x, magicCaretPosition.y);
135        }
136
137        private void showMacroPopupMenu(final String theStartMacroIns, DialogPopupMenu menu) {
138
139                JMenuItem menuItem = new JMenuItem("$var{type:property,type:data}");
140                if ("var{".startsWith(theStartMacroIns)) {
141                        menu.add(menuItem);
142                        menuItem.addActionListener(new ActionListener() {
143                                public void actionPerformed(ActionEvent e) {
144                                        Node object;
145                                        try {
146                                                object = new EasyParser()
147                                                                .getObject("<Data variable_name='type:property' default_value='type:data'/>");
148                                                parsingCommandParamters(object);
149                                                String theStartMacroIns = getStartMacroIns();
150                                                String text = "$var{" + object.getAttribute("variable_name") + ","
151                                                                + object.getAttribute("default_value") + "}";
152                                                insertText(theStartMacroIns, text);
153                                        } catch (Exception e1) {
154                                                log.error(XML_PARSIONG_FAILED_MESSAGE, e1);
155                                        }
156                                }
157                        });
158                }
159
160                menuItem = new JMenuItem("$tag{type:property,type:data}");
161                if ("tag{".startsWith(theStartMacroIns)) {
162                        menu.add(menuItem);
163                        menuItem.addActionListener(new ActionListener() {
164                                public void actionPerformed(ActionEvent e) {
165                                        Node object;
166                                        try {
167                                                object = new EasyParser()
168                                                                .getObject("<Data variable_name='type:property' default_value='type:data'/>");
169                                                parsingCommandParamters(object);
170
171                                                String theStartMacroIns = getStartMacroIns();
172
173                                                insertText(theStartMacroIns, "$tag{" + object.getAttribute("variable_name") + ","
174                                                                + object.getAttribute("default_value") + "}");
175                                        } catch (Exception e1) {
176                                                log.error(XML_PARSIONG_FAILED_MESSAGE, e1);
177                                        }
178                                }
179                        });
180                }
181
182                if ("call{".startsWith(theStartMacroIns)) {
183                        menuItem = new JMenuItem("$call{type:task,type:data}");
184                        menu.add(menuItem);
185                        menuItem.addActionListener(new ActionListener() {
186                                public void actionPerformed(ActionEvent e) {
187                                        Node object;
188                                        try {
189                                                object = new EasyParser()
190                                                                .getObject("<Data task_name='type:task' return_variable='type:property'/>");
191                                                parsingCommandParamters(object);
192                                                String return_variable = object.getAttribute("return_variable");
193                                                insertText(getStartMacroIns(), "$call{" + object.getAttribute("task_name")
194                                                                + (return_variable == null ? "" : "," + return_variable) + "}");
195                                        } catch (Exception e1) {
196                                                log.error(XML_PARSIONG_FAILED_MESSAGE, e1);
197                                        }
198                                }
199                        });
200                }
201
202                if ("file{".startsWith(theStartMacroIns)) {
203                        menuItem = new JMenuItem("$file{type:path}");
204                        menu.add(menuItem);
205                        menuItem.addActionListener(e -> {
206                                Node object;
207                                try {
208                                        object = new EasyParser().getObject("<Data file_name='type:path'/>");
209                                        parsingCommandParamters(object);
210                                        insertText(getStartMacroIns(), "$file{" + object.getAttribute("file_name") + "}");
211                                } catch (Exception e1) {
212                                        log.error(XML_PARSIONG_FAILED_MESSAGE, e1);
213                                }
214                        });
215                }
216        }
217
218        private JPopupMenu showCommandPopupMenu(final String theStartCommand, JPopupMenu menu, Processor makeProcessor) {
219                Map<Class<? extends Processor>, List<String>> namesMap = new LinkedHashMap<>();
220
221                Class<? extends Processor> processorClass = makeProcessor.getClass();
222                Method[] methods = processorClass.getMethods();
223
224                for (Method method : methods) {
225                        if (StringUtils.startsWith(method.getName(), RUN_COMMAND_METHODE_PREFIX)) {
226                                Class<?> declaringClass = method.getDeclaringClass();
227
228                                String name = StringUtils.substringAfter(method.getName(), RUN_COMMAND_METHODE_PREFIX);
229                                if (name.startsWith(theStartCommand) && ClassUtils.isAssignable(declaringClass, Processor.class)) {
230                                        List<String> list = namesMap.get(declaringClass);
231                                        if (list == null) {
232                                                list = new ArrayList<>();
233                                                @SuppressWarnings("unchecked")
234                                                Class<? extends Processor> clazz = (Class<? extends Processor>)declaringClass;
235                                                namesMap.put(clazz, list);
236                                        }
237                                        list.add(name);
238                                }
239                        }
240                }
241
242                Set<Class<? extends Processor>> keySet = namesMap.keySet();
243                boolean first = true;
244                for (Class<? extends Processor> processor : keySet) {
245                        List<String> list = namesMap.get(processor);
246                        String[] names = list.toArray(new String[list.size()]);
247
248                        String name = processor.getName();
249                        if (StringUtils.equals(name, BaseProcessor.class.getName())) {
250                                name = "Base Processor";
251                        }
252
253                        if (first) {
254                                first = false;
255                                List<JMenu> createPopupMenu = createPopupMenu(theStartCommand, processor, names);
256                                for (JMenu jMenu : createPopupMenu) {
257                                        menu.add(jMenu);
258                                }
259
260                        } else {
261
262                                JMenu popupMenu = new JMenu(name);
263                                List<JMenu> createPopupMenu = createPopupMenu(theStartCommand, processor, names);
264                                for (JMenu jMenu : createPopupMenu) {
265                                        popupMenu.add(jMenu);
266                                }
267
268                                popupMenu.setFont(POPUP_MENU_FONT);
269                                popupMenu.setForeground(Color.BLUE.darker());
270                                menu.add(popupMenu);
271
272                        }
273
274                }
275                return menu;
276
277        }
278
279        private List<JMenu> createPopupMenu(final String startSymbols, Class class1, String[] names) {
280                List<JMenu> menu = new ArrayList<>();
281                Arrays.sort(names);
282                for (final String name : names) {
283                        String[] examples = null;
284                        try {
285                                Method method = null;
286                                try {
287                                        method = class1.getMethod(RUN_COMMAND_METHODE_PREFIX + name, new Class[] { Node.class });
288                                        CommandExamples annotation = method.getAnnotation(CommandExamples.class);
289                                        if (annotation != null) {
290                                                examples = annotation.value();
291                                        }
292
293                                } catch (NoSuchMethodException e) {
294                                }
295
296                                CommandHotHepl hotHelp = method.getAnnotation(CommandHotHepl.class);
297
298                                if (examples != null) {
299
300                                        JMenu popupMenu = new JMenu(name) {
301                                                public JToolTip createToolTip() {
302                                                        return new DialogPopupMenu.JMultiLineToolTip();
303                                                }
304                                        };
305
306                                        if (hotHelp != null)
307                                                popupMenu.setToolTipText(hotHelp.value());
308                                        menu.add(popupMenu);
309
310                                        for (String example : examples) {
311                                                JMenuItem item = new JMenuItem(example);
312                                                item.addActionListener(new AbstractAction(example) {
313
314                                                        public void actionPerformed(ActionEvent arg0) {
315                                                                insertTag(startSymbols, arg0.getActionCommand());
316                                                        }
317
318                                                });
319                                                item.setFont(new Font("Arial", Font.ITALIC, 9));
320                                                popupMenu.add(item);
321                                        }
322                                        popupMenu.setFont(POPUP_MENU_FONT);
323                                }
324
325                        } catch (Exception e1) {
326                                log.error("Popup menu creation failed.", e1);
327                        }
328                }
329
330                return menu;
331        }
332
333        private void insertTag(final String startSymbols, String text) {
334                try {
335                        Node object = new EasyParser().getObject("<example>" + text + "</example>");
336                        parsingCommandParamters(object);
337                        StringBuilder code = new StringBuilder();
338                        for (Node node : object) {
339                                code.append(node.getXMLText());
340                        }
341                        insertText(startSymbols, code.toString());
342                } catch (TaskCancelingException e) {
343                } catch (Exception e) {
344                        log.error("Tag wizard failed.", e);
345                        JOptionPane.showMessageDialog(JOptionPane.getRootFrame(), e.getMessage());
346                }
347        }
348
349        private boolean parsingCommandParamters(Node node) {
350                boolean isHelpRequired = true;
351                Properties attributes = new Properties();
352                Node example = node;
353                if (example.isEmpty()) {
354                        example = new Node("example");
355                        example.add(node);
356                }
357                for (Node object : example) {
358                        for (Object name : object.getAttributes().keySet()) {
359                                String attribute = object.getAttribute((String) name);
360                                String theName = ((String) name).replace('_', ' ');
361                                if (attribute.startsWith("type:")) {
362                                        attribute = attribute.substring(5);
363                                        JRootPane rootPane = SwingUtilities.getRootPane(editor.getEditor());
364                                        if (attribute.equals("integer")) {
365                                                boolean valid = true;
366                                                do {
367                                                        attribute = JOptionPane.showInputDialog(rootPane,
368                                                                        "Type: " + attribute + "\nParameter: " + theName);
369                                                        if (attribute == null) {
370                                                                isHelpRequired = false;
371                                                                break;
372                                                        }
373
374                                                        object.setAttribute((String) name, attribute);
375
376                                                } while (!valid);
377
378                                        } else if (attribute.equals("operation")) {
379                                                Map<String, OperationHolder> operationsMethods = AEWorkspace.getInstance()
380                                                                .getOperationsMethods();
381                                                Set<String> keySet = operationsMethods.keySet();
382                                                showListDialog(object, name, attribute, keySet.toArray(new String[keySet.size()]), false,
383                                                                "Operations:");
384                                                String operationName = object.getAttribute("method");
385                                                if (!StringUtils.isEmpty(operationName)) {
386                                                        OperationHolder operationsMethod = AEWorkspace.getInstance()
387                                                                        .getOperationsMethod(operationName);
388                                                        object.setAttribute("description", operationsMethod.getDescription());
389                                                        Processor processor = editor.getTaskProcessor();
390                                                        String[] array = processor.getVariables().keySet().toArray(new String[keySet.size()]);
391                                                        Class<?> returnType = operationsMethod.getMethod().getReturnType();
392                                                        if (returnType != void.class) {
393                                                                object.setAttribute("name", "type:property");
394
395                                                                if (!showListDialog(object, "name", "property", array, true, "Return: "
396                                                                                + operationsMethod.getReturnDescription() + "\nSelect variable name:")) {
397                                                                        isHelpRequired = false;
398                                                                        break;
399                                                                }
400                                                        }
401
402                                                        Class<?>[] parameterTypes = operationsMethod.getMethod().getParameterTypes();
403                                                        for (int i = 0; i < parameterTypes.length; i++) {
404                                                                String type = parameterTypes[i].getName();
405
406                                                                object.setAttribute("arg" + (i + 1), type);
407                                                                String value = (String) JOptionPane.showInputDialog(rootPane,
408                                                                                "Type: " + type + "\nParameter: " + operationsMethod.getParameterName(i),
409                                                                                "Input parameter", JOptionPane.INFORMATION_MESSAGE, null, null, null);
410                                                                if (attribute == null) {
411                                                                        isHelpRequired = false;
412                                                                        break;
413                                                                }
414
415                                                                object.setAttribute("arg" + (i + 1), value);
416                                                        }
417                                                }
418                                                isHelpRequired = false;
419                                                break;
420                                        } else if (attribute.equals("attr")) {
421                                                attribute = attributes.getProperty(theName);
422                                                object.setAttribute((String) name, attribute);
423                                        } else if (attribute.equals("string")) {
424                                                attribute = JOptionPane.showInputDialog(rootPane, "Parameter: " + theName + "\nType: string",
425                                                                "");
426                                                if (attribute == null) {
427                                                        isHelpRequired = false;
428                                                        break;
429                                                }
430                                                object.setAttribute((String) name, attribute);
431                                        } else if (attribute.equals("ms")) {
432                                                attribute = JOptionPane.showInputDialog(rootPane,
433                                                                "Parameter: " + theName + "\nType: milliseconds", "");
434                                                if (attribute == null) {
435                                                        isHelpRequired = false;
436                                                        break;
437                                                }
438                                                object.setAttribute((String) name, attribute);
439                                        } else if (attribute.equals("task")) {
440                                                if (!showListDialog(object, name, attribute, editor.getManager().getTestsList(), true)) {
441                                                        isHelpRequired = false;
442                                                        break;
443                                                }
444                                        } else if (attribute.equals("double")) {
445                                                attribute = JOptionPane.showInputDialog(rootPane, "Parameter: " + theName + "\nType: double",
446                                                                "");
447                                                if (attribute == null) {
448                                                        isHelpRequired = false;
449                                                        break;
450                                                }
451                                                object.setAttribute((String) name, attribute);
452                                        } else if (attribute.equals("property")) {
453                                                Processor processor = editor.getTaskProcessor();
454                                                if (processor == null)
455                                                        processor = new BaseProcessor(editor.getManager(), log, editor.getManager().getStartDir());
456                                                Set<String> keySet = processor.getVariables().keySet();
457                                                if (!showListDialog(object, name, attribute, keySet.toArray(new String[keySet.size()]), true)) {
458                                                        isHelpRequired = false;
459                                                        break;
460                                                }
461                                        } else if (attribute.equals("path")) {
462                                                FileDialog theFileDialog = new FileDialog(JOptionPane.getRootFrame(), "Input path",
463                                                                FileDialog.LOAD);
464                                                String absolutePath = editor.getTaskProcessor().getBaseDir().getAbsolutePath();
465                                                theFileDialog.setDirectory(absolutePath);
466                                                theFileDialog.setVisible(true);
467                                                if (theFileDialog.getFile() != null) {
468                                                        String prefDir = theFileDialog.getDirectory();
469                                                        if (theFileDialog.getDirectory().startsWith(absolutePath)) {
470                                                                prefDir = prefDir.substring(absolutePath.length() + 1, prefDir.length());
471                                                        }
472                                                        object.setAttribute((String) name, prefDir + theFileDialog.getFile());
473                                                }
474                                        }
475
476                                } else if (attribute.startsWith("enum:")) {
477                                        String type = attribute.substring(0, 4);
478                                        String choice = attribute.substring(5);
479                                        StringTokenizer stringTokenizer = new StringTokenizer(choice, "|");
480                                        String[] choiceArray = new String[stringTokenizer.countTokens()];
481                                        for (int i = 0; i < choiceArray.length; i++) {
482                                                choiceArray[i] = stringTokenizer.nextToken();
483                                        }
484                                        if (!showListDialog(object, name, type, choiceArray, false)) {
485                                                isHelpRequired = false;
486                                                break;
487                                        }
488                                }
489                        }
490                        attributes.putAll(object.getAttributes());
491                }
492                return isHelpRequired;
493        }
494
495        private String getStartMacroIns() {
496                int curpos = editor.getCaretPosition();
497                String text = editor.getText();
498                String theStartMacroIns = null;
499
500                int i;
501                for (i = curpos - 1; i >= 0; i--) {
502                        char charAt = text.charAt(i);
503
504                        if (charAt == '$') {
505                                theStartMacroIns = text.substring(i + 1, curpos);
506                                break;
507                        }
508                }
509
510                return (i > curpos - 7) ? theStartMacroIns : "";
511        }
512
513        private void insertText(String theStartCommand, String text) {
514                int caretPosition2 = editor.getCaretPosition();
515                int i = caretPosition2 - theStartCommand.length() - 1;
516                editor.replaceRange(text, i, caretPosition2);
517        }
518
519        private boolean showListDialog(Node object, Object name, String type, String[] sourceList, boolean b) {
520                return showListDialog(object, name, type, sourceList, b, null);
521        }
522
523        private boolean showListDialog(Node object, Object name, String type, String[] sourceList, boolean b,
524                        String description) {
525
526                Arrays.sort(sourceList);
527                String prefix = "Parameter: ";
528
529                String message = StringUtils.defaultString(description, prefix + name + ", Type: " + type);
530                JRootPane rootPane = SwingUtilities.getRootPane(editor.getEditor());
531                String value = OptionPane.showInputDialog(rootPane, message, "Input command attribute", sourceList, "");
532
533                if (value == null) {
534                        return false;
535                }
536
537                object.setAttribute((String) name, value);
538                return true;
539        }
540
541        @Override
542        public void mouseClicked(MouseEvent e) {
543                if (e.getButton() == MouseEvent.BUTTON3) {
544                        showCommandsMenu();
545                }
546        }
547
548        @Override
549        public void mousePressed(MouseEvent e) {
550        }
551
552        @Override
553        public void mouseReleased(MouseEvent e) {
554        }
555
556        @Override
557        public void mouseEntered(MouseEvent e) {
558        }
559
560        @Override
561        public void mouseExited(MouseEvent e) {
562        }
563
564        public void setLog(ILogger log) {
565                this.log = log;
566        }
567
568}