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().getTaskProcessor();
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) || StringUtils.startsWith(name, text))
138                                                && ClassUtils.isAssignable(declaringClass, Processor.class)) {
139
140                                        CommandDescription annotation = method.getAnnotation(CommandDescription.class);
141                                        String description = null;
142                                        if (annotation != null) {
143                                                description = annotation.value();
144                                        }
145
146                                        String commandName;
147
148                                        if (StringUtils.isBlank(name)) {
149                                                name = method.getName();
150                                                commandName = name;
151                                        } else {
152                                                commandName = CodeHelper.RUN_COMMAND_METHODE_PREFIX + name;
153                                        }
154
155                                        if (annotation != null) {
156                                                description = annotation.value();
157                                        }
158
159                                        CommandInfo info = new CommandInfo(name, declaringClass, description);
160
161                                        List<String> exampleList = getExampleList(processorClass, commandName);
162
163                                        info.setExamples(exampleList);
164                                        list.add(info);
165                                }
166                        }
167                }
168                return list;
169
170        }
171
172        public static String findLastUnclosedTag(String xmlText) {
173                Stack<String> tagStack = new Stack<>();
174                int index = 0;
175
176                while (index < xmlText.length()) {
177                        int openTagStart = xmlText.indexOf("<", index);
178
179                        // No more tags found
180                        if (openTagStart == -1) {
181                                break;
182                        }
183
184                        int openTagEnd = xmlText.indexOf(">", openTagStart);
185                        if (openTagEnd == -1) {
186                                break; // Malformed tag, end of string
187                        }
188
189                        String tagContent = xmlText.substring(openTagStart + 1, openTagEnd).trim();
190
191                        // Check for closing tag
192                        if (tagContent.startsWith("/")) {
193                                String closingTag = tagContent.substring(1);
194
195                                if (!tagStack.isEmpty() && tagStack.peek().equals(closingTag)) {
196                                        tagStack.pop(); // Properly closed tag
197                                } else {
198                                        // Closing tag without a matching opening tag
199                                        return closingTag;
200                                }
201                        } else if (!tagContent.endsWith("/")) {
202                                // Opening tag (not self-closing)
203                                int spaceIndex = tagContent.indexOf(" ");
204                                String tagName = (spaceIndex == -1) ? tagContent : tagContent.substring(0, spaceIndex);
205                                tagStack.push(tagName);
206                        }
207
208                        index = openTagEnd + 1;
209                }
210
211                // Return the last unclosed tag, if any
212                return tagStack.isEmpty() ? null : tagStack.peek();
213        }
214
215        public TaskEditor getRecipePanel() {
216                return editor.getRecipePanel();
217        }
218
219        public TextEditor getEditor() {
220                return editor;
221        }
222
223        public void hide() {
224                dialog.setVisible(false);
225        }
226
227        public void setDefaultDialog(HelperDialog defaultDialog) {
228                this.defaultDialog = defaultDialog;
229        }
230
231        public List<String> getExampleList(Class<?> class1, String methodName) {
232                String[] examples = null;
233                List<String> exampleList = new ArrayList<String>();
234                try {
235                        Method method = null;
236                        try {
237                                CommandExamples annotation = null;
238                                if ("init".equals(methodName)) {
239                                        method = class1.getMethod(methodName, new Class[] { Processor.class, Node.class });
240                                        annotation = method.getAnnotation(CommandExamples.class);
241                                        if (annotation == null) {
242                                                method = class1.getMethod(methodName, new Class[] {});
243                                                annotation = method.getAnnotation(CommandExamples.class);
244                                        }
245
246                                } else {
247                                        method = class1.getMethod(methodName, new Class[] { Node.class });
248                                        annotation = method.getAnnotation(CommandExamples.class);
249                                }
250
251                                if (annotation != null) {
252                                        examples = annotation.value();
253                                }
254
255                        } catch (NoSuchMethodException e) {
256                        }
257
258                        if (examples != null) {
259                                for (String example : examples) {
260                                        exampleList.add(example);
261                                }
262                        }
263
264                } catch (Exception e1) {
265                        // log.error("Popup menu creation failed.", e1);
266                }
267
268                return exampleList;
269        }
270
271}