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}