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