001package com.ganteater.ae.desktop.editor;
002
003import java.awt.event.KeyAdapter;
004import java.awt.event.KeyEvent;
005import java.awt.event.MouseAdapter;
006import java.awt.event.MouseEvent;
007import java.lang.reflect.Method;
008import java.util.ArrayList;
009import java.util.List;
010import java.util.Stack;
011
012import org.apache.commons.lang.ClassUtils;
013import org.apache.commons.lang.StringUtils;
014
015import com.ganteater.ae.processor.Processor;
016import com.ganteater.ae.processor.annotation.CommandDescription;
017import com.ganteater.ae.processor.annotation.CommandExamples;
018import com.ganteater.ae.processor.annotation.CommandInfo;
019import com.ganteater.ae.util.xml.easyparser.EasyParser;
020import com.ganteater.ae.util.xml.easyparser.Node;
021
022public class CodeHelper extends KeyAdapter {
023
024        static final String RUN_COMMAND_METHODE_PREFIX = "runCommand";
025
026        private TextEditor editor;
027        private CommandHelperDialog dialog;
028
029        private HelperDialog defaultDialog;
030
031        public CodeHelper(TextEditor recipeEditor) {
032                this.editor = recipeEditor;
033                dialog = createPopup();
034        }
035
036        protected CommandHelperDialog createPopup() {
037                return new CommandHelperDialog(this);
038        }
039
040        @Override
041        public void keyPressed(KeyEvent e) {
042                int keyCode = e.getKeyCode();
043                if (keyCode == KeyEvent.VK_D && e.isControlDown()) {
044                        editor.removeLine();
045                } else if (keyCode == KeyEvent.VK_F9) {
046                        editor.getRecipePanel().runTask();
047                } else if (keyCode == KeyEvent.VK_SPACE && e.isControlDown()) {
048                        showCommandsMenu();
049                }
050        }
051
052        @Override
053        public void keyTyped(KeyEvent e) {
054                if (e.getKeyChar() == '<') {
055                        // showCommandsMenu();
056                }
057        }
058
059        private void showCommandsMenu() {
060                String selectedText = editor.getSelectedText();
061
062                String text = editor.getText();
063                int curpos = editor.getCaretPosition();
064
065                Processor makeProcessor = editor.getRecipePanel().getTaskProcessor();
066                String substring = StringUtils.substring(text, 0, curpos);
067                int startExternTag = StringUtils.lastIndexOf(substring, "<Extern");
068                int closeExternTag = StringUtils.lastIndexOf(substring, "</Extern");
069                if (closeExternTag < 0 || closeExternTag < startExternTag) {
070                        int endExternTag = StringUtils.indexOf(substring, '>', startExternTag);
071                        String externTag = StringUtils.substring(substring, startExternTag, endExternTag);
072                        if (StringUtils.isNotBlank(externTag)) {
073                                externTag = externTag + "/>";
074                                try {
075                                        Node externNode = new EasyParser().getObject(externTag);
076                                        makeProcessor = makeProcessor.makeProcessor(externNode);
077
078                                } catch (Exception e1) {
079                                        // do nothing.
080                                }
081                        }
082                }
083
084                boolean isCommand = true;
085                String startText = null;
086
087                if (selectedText == null) {
088                        for (int i = curpos - 1; i >= 0 && startText == null; i--) {
089                                char charAt = text.charAt(i);
090                                switch (charAt) {
091                                case '>':
092                                        startText = text.substring(i + 1, curpos).trim();
093                                        break;
094
095                                case '<':
096                                        startText = text.substring(i, curpos);
097                                        break;
098
099                                case '$':
100                                        isCommand = false;
101                                        startText = text.substring(i + 1, curpos);
102                                        break;
103
104                                case '/':
105                                        charAt = text.charAt(i - 1);
106                                        if (charAt == '<') {
107                                                String lastUnclosedTag = findLastUnclosedTag(substring);
108                                                editor.insert(lastUnclosedTag + ">", curpos);
109                                                return;
110                                        }
111                                        break;
112
113                                default:
114                                        startText = null;
115                                }
116                        }
117                }
118
119                if (defaultDialog != null && (StringUtils.isBlank(startText) || StringUtils.startsWith(startText, "<!--")
120                                || !StringUtils.startsWith(startText, "<")) || StringUtils.contains(startText, " ")) {
121                        defaultDialog.showDialog();
122                } else {
123                        dialog.helpWith(startText, isCommand);
124                }
125        }
126
127        public List<CommandInfo> getCommandList(String text, Class<? extends Processor> processorClass) {
128                List<CommandInfo> list = new ArrayList<>();
129
130                Method[] methods = processorClass.getMethods();
131                for (Method method : methods) {
132                        if (StringUtils.startsWith(method.getName(), CodeHelper.RUN_COMMAND_METHODE_PREFIX)
133                                        || (text == null && "init".equals(method.getName()))) {
134                                @SuppressWarnings("unchecked")
135                                Class<? extends Processor> declaringClass = (Class<? extends Processor>) method.getDeclaringClass();
136                                String name = StringUtils.substringAfter(method.getName(), CodeHelper.RUN_COMMAND_METHODE_PREFIX);
137
138                                if (((text == null && declaringClass == processorClass) || StringUtils.startsWith(name, text))
139                                                && ClassUtils.isAssignable(declaringClass, Processor.class)) {
140
141                                        CommandDescription annotation = method.getAnnotation(CommandDescription.class);
142                                        String description = null;
143                                        if (annotation != null) {
144                                                description = annotation.value();
145                                        }
146
147                                        String commandName;
148
149                                        if (StringUtils.isBlank(name)) {
150                                                name = method.getName();
151                                                commandName = name;
152                                        } else {
153                                                commandName = CodeHelper.RUN_COMMAND_METHODE_PREFIX + name;
154                                        }
155
156                                        CommandInfo info = new CommandInfo(name, declaringClass, description);
157
158                                        List<String> exampleList = getExampleList(processorClass, commandName);
159                                        
160                                        info.setExamples(exampleList);
161                                        list.add(info);
162                                }
163                        }
164                }
165                return list;
166
167        }
168
169        public static String findLastUnclosedTag(String xmlText) {
170                Stack<String> tagStack = new Stack<>();
171                int index = 0;
172
173                while (index < xmlText.length()) {
174                        int openTagStart = xmlText.indexOf("<", index);
175
176                        // No more tags found
177                        if (openTagStart == -1) {
178                                break;
179                        }
180
181                        int openTagEnd = xmlText.indexOf(">", openTagStart);
182                        if (openTagEnd == -1) {
183                                break; // Malformed tag, end of string
184                        }
185
186                        String tagContent = xmlText.substring(openTagStart + 1, openTagEnd).trim();
187
188                        // Check for closing tag
189                        if (tagContent.startsWith("/")) {
190                                String closingTag = tagContent.substring(1);
191
192                                if (!tagStack.isEmpty() && tagStack.peek().equals(closingTag)) {
193                                        tagStack.pop(); // Properly closed tag
194                                } else {
195                                        // Closing tag without a matching opening tag
196                                        return closingTag;
197                                }
198                        } else if (!tagContent.endsWith("/")) {
199                                // Opening tag (not self-closing)
200                                int spaceIndex = tagContent.indexOf(" ");
201                                String tagName = (spaceIndex == -1) ? tagContent : tagContent.substring(0, spaceIndex);
202                                tagStack.push(tagName);
203                        }
204
205                        index = openTagEnd + 1;
206                }
207
208                // Return the last unclosed tag, if any
209                return tagStack.isEmpty() ? null : tagStack.peek();
210        }
211
212        public TaskEditor getRecipePanel() {
213                return editor.getRecipePanel();
214        }
215
216        public TextEditor getEditor() {
217                return editor;
218        }
219
220        public void hide() {
221                dialog.setVisible(false);
222        }
223
224        public void setDefaultDialog(HelperDialog defaultDialog) {
225                this.defaultDialog = defaultDialog;
226        }
227
228        public void applyListeners(TextEditor textEditor) {
229                textEditor.addMouseListener(new MouseAdapter() {
230                        @Override
231                        public void mouseClicked(MouseEvent e) {
232                                if (e.getButton() == MouseEvent.BUTTON3) {
233                                        showCommandsMenu();
234                                }
235                        }
236                });
237                textEditor.addKeyListener(this);
238        }
239
240        public List<String> getExampleList(Class<? extends Processor> class1, String methodName) {
241                String[] examples = null;
242                List<String> exampleList = new ArrayList<String>();
243                try {
244                        Method method = null;
245                        try {
246                                CommandExamples annotation = null;
247                                if ("init".equals(methodName)) {
248                                        method = class1.getMethod(methodName, new Class[] { Processor.class, Node.class });
249                                        annotation = method.getAnnotation(CommandExamples.class);
250                                        if (annotation == null) {
251                                                method = class1.getMethod(methodName, new Class[] {});
252                                                annotation = method.getAnnotation(CommandExamples.class);
253                                        }
254
255                                } else {
256                                        method = class1.getMethod(methodName, new Class[] { Node.class });
257                                        annotation = method.getAnnotation(CommandExamples.class);
258                                }
259
260                                if (annotation != null) {
261                                        examples = annotation.value();
262                                }
263
264                        } catch (NoSuchMethodException e) {
265                        }
266
267                        if (examples != null) {
268                                for (String example : examples) {
269                                        exampleList.add(example);
270                                }
271                        }
272
273                } catch (Exception e1) {
274                        // log.error("Popup menu creation failed.", e1);
275                }
276
277                return exampleList;
278        }
279
280}