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