001package com.ganteater.ae.desktop.editor; 002 003import java.awt.Color; 004import java.awt.FileDialog; 005import java.awt.Font; 006import java.awt.Point; 007import java.awt.event.ActionEvent; 008import java.awt.event.ActionListener; 009import java.awt.event.KeyAdapter; 010import java.awt.event.KeyEvent; 011import java.awt.event.MouseEvent; 012import java.awt.event.MouseListener; 013import java.lang.reflect.Method; 014import java.util.ArrayList; 015import java.util.Arrays; 016import java.util.LinkedHashMap; 017import java.util.List; 018import java.util.Map; 019import java.util.Properties; 020import java.util.Set; 021import java.util.StringTokenizer; 022 023import javax.swing.AbstractAction; 024import javax.swing.JMenu; 025import javax.swing.JMenuItem; 026import javax.swing.JOptionPane; 027import javax.swing.JPopupMenu; 028import javax.swing.JRootPane; 029import javax.swing.JToolTip; 030import javax.swing.SwingUtilities; 031 032import org.apache.commons.lang.ClassUtils; 033import org.apache.commons.lang.StringUtils; 034 035import com.ganteater.ae.AEWorkspace; 036import com.ganteater.ae.ILogger; 037import com.ganteater.ae.OperationHolder; 038import com.ganteater.ae.TaskCancelingException; 039import com.ganteater.ae.desktop.ui.DialogPopupMenu; 040import com.ganteater.ae.desktop.ui.OptionPane; 041import com.ganteater.ae.processor.BaseProcessor; 042import com.ganteater.ae.processor.Processor; 043import com.ganteater.ae.processor.annotation.CommandExamples; 044import com.ganteater.ae.processor.annotation.CommandHotHepl; 045import com.ganteater.ae.util.xml.easyparser.EasyParser; 046import com.ganteater.ae.util.xml.easyparser.Node; 047 048public class CodeHelper extends KeyAdapter implements MouseListener { 049 050 private static final String RUN_COMMAND_METHODE_PREFIX = "runCommand"; 051 private static final String XML_PARSIONG_FAILED_MESSAGE = "XML parsiong failed."; 052 053 public static final Font POPUP_MENU_FONT = new Font("Arial", Font.PLAIN, 9); 054 055 private AeEditPanel editor; 056 private ILogger log; 057 058 public CodeHelper(AeEditPanel editor, ILogger log) { 059 this.editor = editor; 060 this.log = log; 061 } 062 063 @Override 064 public void keyPressed(KeyEvent e) { 065 066 if (e.getKeyCode() == KeyEvent.VK_D && e.isControlDown()) { 067 editor.getEditor().removeLine(); 068 } else if (e.getKeyCode() == KeyEvent.VK_F9) { 069 editor.runTask(); 070 } else if (e.getKeyCode() == KeyEvent.VK_F && e.isControlDown()) { 071 } else if (e.getKeyCode() == KeyEvent.VK_SPACE && e.isControlDown()) { 072 showCommandsMenu(); 073 } 074 } 075 076 private void showCommandsMenu() { 077 String text = editor.getText(); 078 int curpos = editor.getCaretPosition(); 079 080 Processor makeProcessor = editor.getTaskProcessor(); 081 String substring = StringUtils.substring(text, 0, curpos); 082 int startExternTag = StringUtils.lastIndexOf(substring, "<Extern"); 083 int closeExternTag = StringUtils.lastIndexOf(substring, "</Extern"); 084 if (closeExternTag < 0 || closeExternTag < startExternTag) { 085 int endExternTag = StringUtils.indexOf(substring, '>', startExternTag); 086 String externTag = StringUtils.substring(substring, startExternTag, endExternTag); 087 if (StringUtils.isNotBlank(externTag)) { 088 externTag = externTag + "/>"; 089 try { 090 Node externNode = new EasyParser().getObject(externTag); 091 makeProcessor = makeProcessor.makeProcessor(externNode); 092 093 } catch (Exception e1) { 094 log.error(XML_PARSIONG_FAILED_MESSAGE, e1); 095 } 096 } 097 } 098 099 String theStartCommand = null; 100 String theStartMacroIns = null; 101 102 for (int i = curpos - 1; i >= 0; i--) { 103 char charAt = text.charAt(i); 104 if (charAt == ' ') { 105 break; 106 107 } else if (charAt == '<') { 108 theStartCommand = text.substring(i + 1, curpos); 109 110 break; 111 } else if (charAt == '$') { 112 theStartMacroIns = text.substring(i + 1, curpos); 113 break; 114 } else { 115 theStartCommand = ""; 116 } 117 } 118 119 DialogPopupMenu menu = new DialogPopupMenu(editor.getEditor()); 120 menu.setAutoscrolls(true); 121 122 if (theStartCommand != null) { 123 showCommandPopupMenu(theStartCommand, menu, makeProcessor); 124 125 } 126 if (theStartMacroIns != null) { 127 showMacroPopupMenu(theStartMacroIns, menu); 128 } else { 129 menu = editor.contextHelp(menu); 130 } 131 Point magicCaretPosition = editor.getMagicCaretPosition(); 132 if (magicCaretPosition == null) 133 magicCaretPosition = new Point(); 134 menu.show(editor.getEditor(), magicCaretPosition.x, magicCaretPosition.y); 135 } 136 137 private void showMacroPopupMenu(final String theStartMacroIns, DialogPopupMenu menu) { 138 139 JMenuItem menuItem = new JMenuItem("$var{type:property,type:data}"); 140 if ("var{".startsWith(theStartMacroIns)) { 141 menu.add(menuItem); 142 menuItem.addActionListener(new ActionListener() { 143 public void actionPerformed(ActionEvent e) { 144 Node object; 145 try { 146 object = new EasyParser() 147 .getObject("<Data variable_name='type:property' default_value='type:data'/>"); 148 parsingCommandParamters(object); 149 String theStartMacroIns = getStartMacroIns(); 150 String text = "$var{" + object.getAttribute("variable_name") + "," 151 + object.getAttribute("default_value") + "}"; 152 insertText(theStartMacroIns, text); 153 } catch (Exception e1) { 154 log.error(XML_PARSIONG_FAILED_MESSAGE, e1); 155 } 156 } 157 }); 158 } 159 160 menuItem = new JMenuItem("$tag{type:property,type:data}"); 161 if ("tag{".startsWith(theStartMacroIns)) { 162 menu.add(menuItem); 163 menuItem.addActionListener(new ActionListener() { 164 public void actionPerformed(ActionEvent e) { 165 Node object; 166 try { 167 object = new EasyParser() 168 .getObject("<Data variable_name='type:property' default_value='type:data'/>"); 169 parsingCommandParamters(object); 170 171 String theStartMacroIns = getStartMacroIns(); 172 173 insertText(theStartMacroIns, "$tag{" + object.getAttribute("variable_name") + "," 174 + object.getAttribute("default_value") + "}"); 175 } catch (Exception e1) { 176 log.error(XML_PARSIONG_FAILED_MESSAGE, e1); 177 } 178 } 179 }); 180 } 181 182 if ("call{".startsWith(theStartMacroIns)) { 183 menuItem = new JMenuItem("$call{type:task,type:data}"); 184 menu.add(menuItem); 185 menuItem.addActionListener(new ActionListener() { 186 public void actionPerformed(ActionEvent e) { 187 Node object; 188 try { 189 object = new EasyParser() 190 .getObject("<Data task_name='type:task' return_variable='type:property'/>"); 191 parsingCommandParamters(object); 192 String return_variable = object.getAttribute("return_variable"); 193 insertText(getStartMacroIns(), "$call{" + object.getAttribute("task_name") 194 + (return_variable == null ? "" : "," + return_variable) + "}"); 195 } catch (Exception e1) { 196 log.error(XML_PARSIONG_FAILED_MESSAGE, e1); 197 } 198 } 199 }); 200 } 201 202 if ("file{".startsWith(theStartMacroIns)) { 203 menuItem = new JMenuItem("$file{type:path}"); 204 menu.add(menuItem); 205 menuItem.addActionListener(e -> { 206 Node object; 207 try { 208 object = new EasyParser().getObject("<Data file_name='type:path'/>"); 209 parsingCommandParamters(object); 210 insertText(getStartMacroIns(), "$file{" + object.getAttribute("file_name") + "}"); 211 } catch (Exception e1) { 212 log.error(XML_PARSIONG_FAILED_MESSAGE, e1); 213 } 214 }); 215 } 216 } 217 218 private JPopupMenu showCommandPopupMenu(final String theStartCommand, JPopupMenu menu, Processor makeProcessor) { 219 Map<Class<? extends Processor>, List<String>> namesMap = new LinkedHashMap<>(); 220 221 Class<? extends Processor> processorClass = makeProcessor.getClass(); 222 Method[] methods = processorClass.getMethods(); 223 224 for (Method method : methods) { 225 if (StringUtils.startsWith(method.getName(), RUN_COMMAND_METHODE_PREFIX)) { 226 Class<?> declaringClass = method.getDeclaringClass(); 227 228 String name = StringUtils.substringAfter(method.getName(), RUN_COMMAND_METHODE_PREFIX); 229 if (name.startsWith(theStartCommand) && ClassUtils.isAssignable(declaringClass, Processor.class)) { 230 List<String> list = namesMap.get(declaringClass); 231 if (list == null) { 232 list = new ArrayList<>(); 233 @SuppressWarnings("unchecked") 234 Class<? extends Processor> clazz = (Class<? extends Processor>)declaringClass; 235 namesMap.put(clazz, list); 236 } 237 list.add(name); 238 } 239 } 240 } 241 242 Set<Class<? extends Processor>> keySet = namesMap.keySet(); 243 boolean first = true; 244 for (Class<? extends Processor> processor : keySet) { 245 List<String> list = namesMap.get(processor); 246 String[] names = list.toArray(new String[list.size()]); 247 248 String name = processor.getName(); 249 if (StringUtils.equals(name, BaseProcessor.class.getName())) { 250 name = "Base Processor"; 251 } 252 253 if (first) { 254 first = false; 255 List<JMenu> createPopupMenu = createPopupMenu(theStartCommand, processor, names); 256 for (JMenu jMenu : createPopupMenu) { 257 menu.add(jMenu); 258 } 259 260 } else { 261 262 JMenu popupMenu = new JMenu(name); 263 List<JMenu> createPopupMenu = createPopupMenu(theStartCommand, processor, names); 264 for (JMenu jMenu : createPopupMenu) { 265 popupMenu.add(jMenu); 266 } 267 268 popupMenu.setFont(POPUP_MENU_FONT); 269 popupMenu.setForeground(Color.BLUE.darker()); 270 menu.add(popupMenu); 271 272 } 273 274 } 275 return menu; 276 277 } 278 279 private List<JMenu> createPopupMenu(final String startSymbols, Class class1, String[] names) { 280 List<JMenu> menu = new ArrayList<>(); 281 Arrays.sort(names); 282 for (final String name : names) { 283 String[] examples = null; 284 try { 285 Method method = null; 286 try { 287 method = class1.getMethod(RUN_COMMAND_METHODE_PREFIX + name, new Class[] { Node.class }); 288 CommandExamples annotation = method.getAnnotation(CommandExamples.class); 289 if (annotation != null) { 290 examples = annotation.value(); 291 } 292 293 } catch (NoSuchMethodException e) { 294 } 295 296 CommandHotHepl hotHelp = method.getAnnotation(CommandHotHepl.class); 297 298 if (examples != null) { 299 300 JMenu popupMenu = new JMenu(name) { 301 public JToolTip createToolTip() { 302 return new DialogPopupMenu.JMultiLineToolTip(); 303 } 304 }; 305 306 if (hotHelp != null) 307 popupMenu.setToolTipText(hotHelp.value()); 308 menu.add(popupMenu); 309 310 for (String example : examples) { 311 JMenuItem item = new JMenuItem(example); 312 item.addActionListener(new AbstractAction(example) { 313 314 public void actionPerformed(ActionEvent arg0) { 315 insertTag(startSymbols, arg0.getActionCommand()); 316 } 317 318 }); 319 item.setFont(new Font("Arial", Font.ITALIC, 9)); 320 popupMenu.add(item); 321 } 322 popupMenu.setFont(POPUP_MENU_FONT); 323 } 324 325 } catch (Exception e1) { 326 log.error("Popup menu creation failed.", e1); 327 } 328 } 329 330 return menu; 331 } 332 333 private void insertTag(final String startSymbols, String text) { 334 try { 335 Node object = new EasyParser().getObject("<example>" + text + "</example>"); 336 parsingCommandParamters(object); 337 StringBuilder code = new StringBuilder(); 338 for (Node node : object) { 339 code.append(node.getXMLText()); 340 } 341 insertText(startSymbols, code.toString()); 342 } catch (TaskCancelingException e) { 343 } catch (Exception e) { 344 log.error("Tag wizard failed.", e); 345 JOptionPane.showMessageDialog(JOptionPane.getRootFrame(), e.getMessage()); 346 } 347 } 348 349 private boolean parsingCommandParamters(Node node) { 350 boolean isHelpRequired = true; 351 Properties attributes = new Properties(); 352 Node example = node; 353 if (example.isEmpty()) { 354 example = new Node("example"); 355 example.add(node); 356 } 357 for (Node object : example) { 358 for (Object name : object.getAttributes().keySet()) { 359 String attribute = object.getAttribute((String) name); 360 String theName = ((String) name).replace('_', ' '); 361 if (attribute.startsWith("type:")) { 362 attribute = attribute.substring(5); 363 JRootPane rootPane = SwingUtilities.getRootPane(editor.getEditor()); 364 if (attribute.equals("integer")) { 365 boolean valid = true; 366 do { 367 attribute = JOptionPane.showInputDialog(rootPane, 368 "Type: " + attribute + "\nParameter: " + theName); 369 if (attribute == null) { 370 isHelpRequired = false; 371 break; 372 } 373 374 object.setAttribute((String) name, attribute); 375 376 } while (!valid); 377 378 } else if (attribute.equals("operation")) { 379 Map<String, OperationHolder> operationsMethods = AEWorkspace.getInstance() 380 .getOperationsMethods(); 381 Set<String> keySet = operationsMethods.keySet(); 382 showListDialog(object, name, attribute, keySet.toArray(new String[keySet.size()]), false, 383 "Operations:"); 384 String operationName = object.getAttribute("method"); 385 if (!StringUtils.isEmpty(operationName)) { 386 OperationHolder operationsMethod = AEWorkspace.getInstance() 387 .getOperationsMethod(operationName); 388 object.setAttribute("description", operationsMethod.getDescription()); 389 Processor processor = editor.getTaskProcessor(); 390 String[] array = processor.getVariables().keySet().toArray(new String[keySet.size()]); 391 Class<?> returnType = operationsMethod.getMethod().getReturnType(); 392 if (returnType != void.class) { 393 object.setAttribute("name", "type:property"); 394 395 if (!showListDialog(object, "name", "property", array, true, "Return: " 396 + operationsMethod.getReturnDescription() + "\nSelect variable name:")) { 397 isHelpRequired = false; 398 break; 399 } 400 } 401 402 Class<?>[] parameterTypes = operationsMethod.getMethod().getParameterTypes(); 403 for (int i = 0; i < parameterTypes.length; i++) { 404 String type = parameterTypes[i].getName(); 405 406 object.setAttribute("arg" + (i + 1), type); 407 String value = (String) JOptionPane.showInputDialog(rootPane, 408 "Type: " + type + "\nParameter: " + operationsMethod.getParameterName(i), 409 "Input parameter", JOptionPane.INFORMATION_MESSAGE, null, null, null); 410 if (attribute == null) { 411 isHelpRequired = false; 412 break; 413 } 414 415 object.setAttribute("arg" + (i + 1), value); 416 } 417 } 418 isHelpRequired = false; 419 break; 420 } else if (attribute.equals("attr")) { 421 attribute = attributes.getProperty(theName); 422 object.setAttribute((String) name, attribute); 423 } else if (attribute.equals("string")) { 424 attribute = JOptionPane.showInputDialog(rootPane, "Parameter: " + theName + "\nType: string", 425 ""); 426 if (attribute == null) { 427 isHelpRequired = false; 428 break; 429 } 430 object.setAttribute((String) name, attribute); 431 } else if (attribute.equals("ms")) { 432 attribute = JOptionPane.showInputDialog(rootPane, 433 "Parameter: " + theName + "\nType: milliseconds", ""); 434 if (attribute == null) { 435 isHelpRequired = false; 436 break; 437 } 438 object.setAttribute((String) name, attribute); 439 } else if (attribute.equals("task")) { 440 if (!showListDialog(object, name, attribute, editor.getManager().getTestsList(), true)) { 441 isHelpRequired = false; 442 break; 443 } 444 } else if (attribute.equals("double")) { 445 attribute = JOptionPane.showInputDialog(rootPane, "Parameter: " + theName + "\nType: double", 446 ""); 447 if (attribute == null) { 448 isHelpRequired = false; 449 break; 450 } 451 object.setAttribute((String) name, attribute); 452 } else if (attribute.equals("property")) { 453 Processor processor = editor.getTaskProcessor(); 454 if (processor == null) 455 processor = new BaseProcessor(editor.getManager(), log, editor.getManager().getStartDir()); 456 Set<String> keySet = processor.getVariables().keySet(); 457 if (!showListDialog(object, name, attribute, keySet.toArray(new String[keySet.size()]), true)) { 458 isHelpRequired = false; 459 break; 460 } 461 } else if (attribute.equals("path")) { 462 FileDialog theFileDialog = new FileDialog(JOptionPane.getRootFrame(), "Input path", 463 FileDialog.LOAD); 464 String absolutePath = editor.getTaskProcessor().getBaseDir().getAbsolutePath(); 465 theFileDialog.setDirectory(absolutePath); 466 theFileDialog.setVisible(true); 467 if (theFileDialog.getFile() != null) { 468 String prefDir = theFileDialog.getDirectory(); 469 if (theFileDialog.getDirectory().startsWith(absolutePath)) { 470 prefDir = prefDir.substring(absolutePath.length() + 1, prefDir.length()); 471 } 472 object.setAttribute((String) name, prefDir + theFileDialog.getFile()); 473 } 474 } 475 476 } else if (attribute.startsWith("enum:")) { 477 String type = attribute.substring(0, 4); 478 String choice = attribute.substring(5); 479 StringTokenizer stringTokenizer = new StringTokenizer(choice, "|"); 480 String[] choiceArray = new String[stringTokenizer.countTokens()]; 481 for (int i = 0; i < choiceArray.length; i++) { 482 choiceArray[i] = stringTokenizer.nextToken(); 483 } 484 if (!showListDialog(object, name, type, choiceArray, false)) { 485 isHelpRequired = false; 486 break; 487 } 488 } 489 } 490 attributes.putAll(object.getAttributes()); 491 } 492 return isHelpRequired; 493 } 494 495 private String getStartMacroIns() { 496 int curpos = editor.getCaretPosition(); 497 String text = editor.getText(); 498 String theStartMacroIns = null; 499 500 int i; 501 for (i = curpos - 1; i >= 0; i--) { 502 char charAt = text.charAt(i); 503 504 if (charAt == '$') { 505 theStartMacroIns = text.substring(i + 1, curpos); 506 break; 507 } 508 } 509 510 return (i > curpos - 7) ? theStartMacroIns : ""; 511 } 512 513 private void insertText(String theStartCommand, String text) { 514 int caretPosition2 = editor.getCaretPosition(); 515 int i = caretPosition2 - theStartCommand.length() - 1; 516 editor.replaceRange(text, i, caretPosition2); 517 } 518 519 private boolean showListDialog(Node object, Object name, String type, String[] sourceList, boolean b) { 520 return showListDialog(object, name, type, sourceList, b, null); 521 } 522 523 private boolean showListDialog(Node object, Object name, String type, String[] sourceList, boolean b, 524 String description) { 525 526 Arrays.sort(sourceList); 527 String prefix = "Parameter: "; 528 529 String message = StringUtils.defaultString(description, prefix + name + ", Type: " + type); 530 JRootPane rootPane = SwingUtilities.getRootPane(editor.getEditor()); 531 String value = OptionPane.showInputDialog(rootPane, message, "Input command attribute", sourceList, ""); 532 533 if (value == null) { 534 return false; 535 } 536 537 object.setAttribute((String) name, value); 538 return true; 539 } 540 541 @Override 542 public void mouseClicked(MouseEvent e) { 543 if (e.getButton() == MouseEvent.BUTTON3) { 544 showCommandsMenu(); 545 } 546 } 547 548 @Override 549 public void mousePressed(MouseEvent e) { 550 } 551 552 @Override 553 public void mouseReleased(MouseEvent e) { 554 } 555 556 @Override 557 public void mouseEntered(MouseEvent e) { 558 } 559 560 @Override 561 public void mouseExited(MouseEvent e) { 562 } 563 564 public void setLog(ILogger log) { 565 this.log = log; 566 } 567 568}