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}