001/** 002 * Copyright (c) 2008-2014 Ardor Labs, Inc. 003 * 004 * This file is part of Ardor3D. 005 * 006 * Ardor3D is free software: you can redistribute it and/or modify it 007 * under the terms of its license which may be found in the accompanying 008 * LICENSE file or at <http://www.ardor3d.com/LICENSE>. 009 */ 010 011package com.ardor3d.example; 012 013import java.awt.BorderLayout; 014import java.awt.Color; 015import java.awt.Component; 016import java.awt.Dimension; 017import java.awt.FlowLayout; 018import java.awt.Font; 019import java.awt.event.ActionEvent; 020import java.io.BufferedReader; 021import java.io.File; 022import java.io.FileFilter; 023import java.io.IOException; 024import java.io.InputStream; 025import java.io.InputStreamReader; 026import java.io.UnsupportedEncodingException; 027import java.net.JarURLConnection; 028import java.net.URI; 029import java.net.URL; 030import java.net.URLConnection; 031import java.net.URLDecoder; 032import java.util.ArrayList; 033import java.util.Collections; 034import java.util.Comparator; 035import java.util.Enumeration; 036import java.util.HashMap; 037import java.util.LinkedHashMap; 038import java.util.List; 039import java.util.Locale; 040import java.util.Map.Entry; 041import java.util.MissingResourceException; 042import java.util.ResourceBundle; 043import java.util.Vector; 044import java.util.jar.JarEntry; 045import java.util.jar.JarFile; 046import java.util.logging.Level; 047import java.util.logging.Logger; 048import java.util.zip.ZipEntry; 049 050import javax.swing.AbstractAction; 051import javax.swing.Action; 052import javax.swing.BorderFactory; 053import javax.swing.ImageIcon; 054import javax.swing.JButton; 055import javax.swing.JComponent; 056import javax.swing.JFrame; 057import javax.swing.JLabel; 058import javax.swing.JOptionPane; 059import javax.swing.JPanel; 060import javax.swing.JScrollPane; 061import javax.swing.JSplitPane; 062import javax.swing.JTabbedPane; 063import javax.swing.JTextArea; 064import javax.swing.JTextField; 065import javax.swing.JToggleButton; 066import javax.swing.JToolBar; 067import javax.swing.JTree; 068import javax.swing.KeyStroke; 069import javax.swing.ScrollPaneConstants; 070import javax.swing.SwingConstants; 071import javax.swing.SwingUtilities; 072import javax.swing.UIManager; 073import javax.swing.WindowConstants; 074import javax.swing.event.DocumentEvent; 075import javax.swing.event.DocumentListener; 076import javax.swing.event.EventListenerList; 077import javax.swing.event.TreeModelEvent; 078import javax.swing.event.TreeModelListener; 079import javax.swing.event.TreeSelectionEvent; 080import javax.swing.event.TreeSelectionListener; 081import javax.swing.text.Document; 082import javax.swing.tree.DefaultTreeCellRenderer; 083import javax.swing.tree.TreeCellRenderer; 084import javax.swing.tree.TreeModel; 085import javax.swing.tree.TreePath; 086import javax.swing.tree.TreeSelectionModel; 087 088import com.ardor3d.util.resource.ResourceLocatorTool; 089 090/** 091 * starter for Ardor3D examples 092 */ 093public class ExampleRunner extends JFrame { 094 095 private static final long serialVersionUID = 1L; 096 private final Logger logger = Logger.getLogger(ExampleRunner.class.getCanonicalName()); 097 private final JTree tree; 098 private final ClassTreeModel model; 099 private final JSplitPane splitPane; 100 private final JLabel lDescription; 101 private final JLabel lStatus; 102 private final Action runSelectedAction; 103 private final Action browseSelectedAction; 104 private final Action firstMatchAction; 105 private final Action nextMatchAction; 106 private final Action previousMatchAction; 107 private final ErasableTextField tfPattern; 108 private final JTabbedPane tabbedPane; 109 private final DisplayConsole console; 110 private int maxHeapMemory = 64; 111 private final static String HEADER = "<html><body><h2 align=\"center\">Ardor3d Examples</h2>" 112 + "<p>Choose an example in the tree and select Run. Movement keys are: W, A, S, and D. Control view with left/right buttons.</p>" 113 + "<p align=\"center\"><img src=\"" + ResourceLocatorTool.getClassPathResource(ExampleRunner.class, 114 "com/ardor3d/example/media/images/ardor3d_white_256.jpg") 115 + "\"></p></body></html>"; 116 private static Comparator<Class<?>> classComparator = new Comparator<Class<?>>() { 117 118 @Override 119 public int compare(final Class<?> o1, final Class<?> o2) { 120 return o1.getCanonicalName().compareTo(o2.getCanonicalName()); 121 } 122 }; 123 124 public ExampleRunner() { 125 setTitle("Ardor3D SDK Examples"); 126 setLayout(new BorderLayout()); 127 128 model = new ClassTreeModel(); 129 tree = new JTree(model); 130 tree.getSelectionModel().setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION); 131 tree.setCellRenderer(new ClassNameCellRenderer(model)); 132 tfPattern = new ErasableTextField(10); 133 tfPattern.getDocument().addDocumentListener(new DocumentListener() { 134 135 @Override 136 public void removeUpdate(final DocumentEvent e) { 137 search(); 138 } 139 140 @Override 141 public void insertUpdate(final DocumentEvent e) { 142 search(); 143 } 144 145 @Override 146 public void changedUpdate(final DocumentEvent e) { 147 search(); 148 } 149 }); 150 final AbstractAction expandAction = new AbstractAction() { 151 152 private static final long serialVersionUID = 1L; 153 154 { 155 // putValue(Action.NAME, "Expand nodes"); 156 putValue(Action.SMALL_ICON, getIcon("view-list-tree.png")); 157 putValue(Action.SHORT_DESCRIPTION, "Expand all branches"); 158 } 159 160 @Override 161 public void actionPerformed(final ActionEvent e) { 162 if (((JToggleButton) e.getSource()).isSelected()) { 163 for (int row = 0; row < tree.getRowCount(); row++) { 164 tree.expandRow(row); 165 } 166 } else { 167 for (int row = 1; row < tree.getRowCount(); row++) { 168 tree.collapseRow(row); 169 } 170 } 171 } 172 }; 173 final JToolBar toolbar = new JToolBar(); 174 toolbar.setFloatable(false); 175 final JToggleButton btExpand = new JToggleButton(expandAction); 176 btExpand.setBorderPainted(false); 177 btExpand.setBorder(null); 178 179 lStatus = new JLabel(" "); 180 final JPanel pTree = new JPanel(new BorderLayout()); 181 final JScrollPane scrTree = new JScrollPane(tree); 182 scrTree.setBorder(BorderFactory.createEmptyBorder(3, 3, 3, 3)); // border layout aligns with toolbarPanel 183 final JPanel pStatus = new JPanel(new FlowLayout(FlowLayout.LEADING)); 184 pStatus.setBorder(BorderFactory.createEtchedBorder()); 185 pStatus.add(lStatus); 186 pTree.add(scrTree); 187 pTree.add(toolbar, BorderLayout.NORTH); 188 pTree.add(pStatus, BorderLayout.SOUTH); 189 190 runSelectedAction = new AbstractAction() { 191 192 private static final long serialVersionUID = 1L; 193 194 { 195 putValue(Action.NAME, "Run"); 196 putValue(Action.SMALL_ICON, getIcon("media-playback-start.png")); 197 putValue(Action.SHORT_DESCRIPTION, "Run the selected example Ctrl-R"); 198 putValue(Action.ACCELERATOR_KEY, KeyStroke.getKeyStroke("control R")); 199 } 200 201 @Override 202 public void actionPerformed(final ActionEvent e) { 203 runSelected(); 204 } 205 }; 206 207 browseSelectedAction = new AbstractAction() { 208 209 private static final long serialVersionUID = 1L; 210 211 { 212 putValue(Action.NAME, "Browse"); 213 putValue(Action.SMALL_ICON, getIcon("world.png")); 214 putValue(Action.SHORT_DESCRIPTION, 215 "View the source code. (Requires active Internet connection.) Ctrl-B"); 216 putValue(Action.ACCELERATOR_KEY, KeyStroke.getKeyStroke("control B")); 217 } 218 219 @Override 220 public void actionPerformed(final ActionEvent e) { 221 browseSelected(); 222 } 223 }; 224 225 previousMatchAction = new AbstractAction() { 226 227 private static final long serialVersionUID = 1L; 228 { 229 putValue(Action.NAME, "Previous match"); 230 putValue(Action.SMALL_ICON, getIcon("go-up-search.png")); 231 putValue(Action.SHORT_DESCRIPTION, "Go to previous match Ctrl-P"); 232 putValue(Action.ACCELERATOR_KEY, KeyStroke.getKeyStroke("control P")); 233 } 234 235 @Override 236 public void actionPerformed(final ActionEvent e) { 237 navigateInMatches(-1); 238 } 239 }; 240 241 nextMatchAction = new AbstractAction() { 242 243 private static final long serialVersionUID = 1L; 244 { 245 putValue(Action.NAME, "Next match"); 246 putValue(Action.SMALL_ICON, getIcon("go-down-search.png")); 247 putValue(Action.SHORT_DESCRIPTION, "Go to next match Ctrl-N"); 248 putValue(Action.ACCELERATOR_KEY, KeyStroke.getKeyStroke("control N")); 249 } 250 251 @Override 252 public void actionPerformed(final ActionEvent e) { 253 navigateInMatches(1); 254 } 255 }; 256 257 firstMatchAction = new AbstractAction() { 258 259 private static final long serialVersionUID = 1L; 260 { 261 putValue(Action.NAME, "First match"); 262 putValue(Action.SMALL_ICON, getIcon("go-top-search.png")); 263 putValue(Action.SHORT_DESCRIPTION, "Go to first match Ctrl-F"); 264 putValue(Action.ACCELERATOR_KEY, KeyStroke.getKeyStroke("control F")); 265 } 266 267 @Override 268 public void actionPerformed(final ActionEvent e) { 269 tree.setSelectionRow(0); 270 navigateInMatches(1); 271 } 272 }; 273 274 toolbar.add(btExpand); 275 toolbar.addSeparator(); 276 toolbar.add(tfPattern); 277 toolbar.add(firstMatchAction); 278 toolbar.add(nextMatchAction); 279 toolbar.add(previousMatchAction); 280 toolbar.addSeparator(); 281 toolbar.add(runSelectedAction); 282 toolbar.add(browseSelectedAction); 283 284 for (final Action action : new Action[] { firstMatchAction, nextMatchAction, previousMatchAction, 285 runSelectedAction, browseSelectedAction }) { 286 toolbar.getActionMap().put(action.getValue(Action.NAME), action); 287 toolbar.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW) 288 .put((KeyStroke) action.getValue(Action.ACCELERATOR_KEY), action.getValue(Action.NAME)); 289 } 290 291 // add a handler for return (enter key) on the tree 292 tree.getActionMap().put(runSelectedAction.getValue(Action.NAME), runSelectedAction); 293 tree.getInputMap().put(KeyStroke.getKeyStroke("ENTER"), runSelectedAction.getValue(Action.NAME)); 294 295 lDescription = new JLabel(); 296 lDescription.setVerticalTextPosition(SwingConstants.TOP); 297 lDescription.setVerticalAlignment(SwingConstants.TOP); 298 lDescription.setHorizontalAlignment(SwingConstants.CENTER); 299 final JPanel pExample = new JPanel(new BorderLayout()); 300 pExample.setBorder(BorderFactory.createEmptyBorder(2, 5, 2, 5)); 301 pExample.add(lDescription); 302 console = new DisplayConsole(); 303 tabbedPane = new JTabbedPane(); 304 tabbedPane.setTabPlacement(SwingConstants.BOTTOM); 305 tabbedPane.addTab("Description", getIcon("declaration.png"), pExample); 306 tabbedPane.addTab("Console", getIcon("console.png"), console); 307 308 splitPane = new JSplitPane(); 309 splitPane.setDividerLocation(300); 310 splitPane.setLeftComponent(pTree); 311 splitPane.setRightComponent(tabbedPane); 312 add(splitPane); 313 314 model.reload("com.ardor3d.example"); 315 btExpand.doClick(); 316 lStatus.setText("Examples: " + model.getSize()); 317 318 tree.getSelectionModel().addTreeSelectionListener(new TreeSelectionListener() { 319 320 @Override 321 public void valueChanged(final TreeSelectionEvent e) { 322 updateDescription(); 323 updateActionStatus(); 324 tabbedPane.setSelectedIndex(0); 325 } 326 }); 327 328 updateDescription(); 329 updateActionStatus(); 330 } 331 332 private void navigateInMatches(final int dir) { 333 int current = tree.getSelectionModel().getLeadSelectionRow(); 334 for (;;) { 335 current += dir; 336 if (current >= tree.getRowCount()) { 337 current = 0; 338 } else if (current < 0) { 339 current = tree.getRowCount() - 1; 340 } 341 final TreePath tp = tree.getPathForRow(current); 342 final Object node = tp.getLastPathComponent(); 343 if (node instanceof Class<?> && model.matches(node)) { 344 tree.setSelectionPath(tp); 345 tree.scrollPathToVisible(tp); 346 return; 347 } 348 } 349 } 350 351 private void search() { 352 final String pattern = tfPattern.getText(); 353 final int matches = model.updateMatches(pattern); 354 tfPattern.setWarning(pattern.length() > 0 && matches == 0); 355 tree.repaint(); 356 lStatus.setText("Examples: " + model.getSize() + (matches > 0 ? (" | matched: " + matches) : "")); 357 updateActionStatus(); 358 } 359 360 private void updateActionStatus() { 361 final TreePath tp = tree.getSelectionPath(); 362 final boolean canRun = tp != null && tp.getLastPathComponent() instanceof Class<?>; 363 runSelectedAction.setEnabled(canRun); 364 browseSelectedAction.setEnabled(canRun); 365 previousMatchAction.setEnabled(model.getMatchCount() > 0); 366 nextMatchAction.setEnabled(model.getMatchCount() > 0); 367 firstMatchAction.setEnabled(model.getMatchCount() > 0); 368 } 369 370 private void updateDescription() { 371 Class<?> selectedClass = null; 372 // just take the first selected node 373 final TreePath tp = tree.getSelectionPath(); 374 if (tp != null) { 375 final Object selected = tp.getLastPathComponent(); 376 if (selected instanceof Class<?>) { 377 selectedClass = (Class<?>) selected; 378 } 379 } 380 if (selectedClass == null) { 381 lDescription.setText(HEADER); 382 } else { 383 final Purpose purpose = selectedClass.getAnnotation(Purpose.class); 384 String imgURL = ""; 385 String htmlDescription = ""; 386 if (purpose != null) { 387 try { 388 // Look for the example's thumbnail. 389 final URL imageURL = ResourceLocatorTool.getClassPathResource(ExampleRunner.class, 390 purpose.thumbnailPath()); 391 if (imageURL != null) { 392 imgURL = "<br><img src=\"" + imageURL + "\">"; 393 } 394 } catch (final Exception ex) { 395 } 396 397 maxHeapMemory = purpose.maxHeapMemory(); 398 399 htmlDescription = getHtmlDescription(purpose); 400 } 401 // default to Ardor3D logo if no image available. 402 if ("".equals(imgURL)) { 403 imgURL = "<img src=\"" + ResourceLocatorTool.getClassPathResource(ExampleRunner.class, 404 "com/ardor3d/example/media/images/ardor3d_white_256.jpg") + "\"/>"; 405 } 406 407 // Set the description HTML 408 lDescription.setText("<html><body><h2 align=\"center\">" + selectedClass.getSimpleName() + "</h2>" + "<p>" 409 + htmlDescription + "</p>" + "<p align=\"center\">" + imgURL + "</p></body></html>"); 410 } 411 } 412 413 private String getHtmlDescription(final Class<?> clazz) { 414 final Purpose purpose = clazz.getAnnotation(Purpose.class); 415 return getHtmlDescription(purpose); 416 } 417 418 private String getHtmlDescription(final Purpose purpose) { 419 if (purpose == null) { 420 return ""; 421 } 422 try { 423 final ResourceBundle bundle = ResourceBundle.getBundle(purpose.localisationBaseFile(), Locale.getDefault(), 424 ClassLoader.getSystemClassLoader()); 425 426 return bundle.getString(purpose.htmlDescriptionKey()); 427 428 } catch (final MissingResourceException e) { 429 logger.log(Level.WARNING, "no resource for " + purpose.localisationBaseFile(), e); 430 } 431 return ""; 432 } 433 434 private void runSelected() { 435 // just take the first selected node 436 final TreePath tp = tree.getSelectionPath(); 437 if (tp == null) { 438 return; 439 } 440 final Object selected = tp.getLastPathComponent(); 441 if (!(selected instanceof Class<?>)) { 442 return; 443 } 444 new Thread() { 445 446 @Override 447 public void run() { 448 try { 449 final Class<?> clazz = (Class<?>) selected; 450 451 final boolean isWindows = System.getProperty("os.name").contains("Windows"); 452 final List<String> args = new ArrayList<>(); 453 args.add(isWindows ? "javaw" : "java"); 454 args.add("-Xmx" + maxHeapMemory + "M"); 455 args.add("-cp"); 456 args.add(System.getProperty("java.class.path")); 457 args.add("-Djava.library.path=" + System.getProperty("java.library.path")); 458 args.add(clazz.getCanonicalName()); 459 logger.info("start " + args.toString()); 460 final ProcessBuilder pb = new ProcessBuilder(args); 461 pb.redirectErrorStream(true); 462 final Process p = pb.start(); 463 final InputStream in = p.getInputStream(); 464 console.started(clazz.getCanonicalName(), p); 465 new ConsoleStreamer(in, console).start(); 466 } catch (final Exception ex) { 467 JOptionPane.showMessageDialog(ExampleRunner.this, ex.toString()); 468 } 469 } 470 }.start(); 471 } 472 473 private void browseSelected() { 474 // just take the first selected node 475 final TreePath tp = tree.getSelectionPath(); 476 if (tp == null) { 477 return; 478 } 479 final Object selected = tp.getLastPathComponent(); 480 if (!(selected instanceof Class<?>)) { 481 return; 482 } 483 484 String className = ((Class<?>) selected).getCanonicalName(); 485 className = className.replace('.', '/'); 486 final String urlString = "http://ardorlabs.trac.cvsdude.com/Ardor3Dv1/browser/trunk/ardor3d-examples/src/main/java/" 487 + className + ".java"; 488 try { 489 java.awt.Desktop.getDesktop().browse(new URI(urlString)); 490 } catch (final Exception ex) { 491 ex.printStackTrace(); 492 JOptionPane.showMessageDialog(ExampleRunner.this, 493 "Unable to open URL: " + urlString + "\n" + ex.getMessage()); 494 } 495 } 496 497 private static ImageIcon getIcon(final String name) { 498 return new ImageIcon(ResourceLocatorTool.getClassPathResource(ExampleRunner.class, 499 "com/ardor3d/example/media/icons/" + name)); 500 } 501 502 interface SearchFilter { 503 504 public boolean matches(final Object value); 505 506 public int updateMatches(final String p); 507 } 508 509 class ClassTreeModel implements TreeModel, SearchFilter { 510 511 private final EventListenerList listeners = new EventListenerList(); 512 private final LinkedHashMap<Package, Vector<Class<?>>> classes = new LinkedHashMap<>(); 513 // the next two maps are for caching the status for the search filter 514 private final HashMap<Class<?>, Boolean> classMatches = new HashMap<>(); 515 private final HashMap<Package, Boolean> packageMatches = new HashMap<>(); 516 private String root = "all examples"; 517 private FileFilter classFileFilter; 518 private int size; 519 private int matchCount = 0; 520 521 @Override 522 public void addTreeModelListener(final TreeModelListener l) { 523 listeners.add(TreeModelListener.class, l); 524 } 525 526 @Override 527 public Object getChild(final Object parent, final int index) { 528 if (root.equals(parent)) { 529 final Vector<Package> vec = new Vector<>(classes.keySet()); 530 return vec.get(index); 531 } 532 final Vector<Class<?>> cl = classes.get(parent); 533 return cl == null ? null : cl.get(index); 534 } 535 536 @Override 537 public int getChildCount(final Object parent) { 538 if (root.equals(parent)) { 539 return classes.size(); 540 } 541 final Vector<Class<?>> cl = classes.get(parent); 542 return cl == null ? 0 : cl.size(); 543 } 544 545 @Override 546 public int getIndexOfChild(final Object parent, final Object child) { 547 if (root.equals(parent)) { 548 final Vector<Package> vec = new Vector<>(classes.keySet()); 549 return vec.indexOf(child); 550 } 551 final Vector<Class<?>> cl = classes.get(parent); 552 return cl == null ? 0 : cl.indexOf(child); 553 } 554 555 @Override 556 public Object getRoot() { 557 return root; 558 } 559 560 public void addClassForPackage(final Class<?> clazz) { 561 logger.fine("found " + clazz); 562 if (clazz.equals(ExampleRunner.class)) { 563 return; 564 } 565 packageMatches.put(clazz.getPackage(), false); 566 classMatches.put(clazz, false); 567 Vector<Class<?>> cl = classes.get(clazz.getPackage()); 568 if (cl == null) { 569 cl = new Vector<>(); 570 classes.put(clazz.getPackage(), cl); 571 } 572 size++; 573 cl.add(clazz); 574 Collections.sort(cl, classComparator); 575 } 576 577 @Override 578 public int updateMatches(final String pattern) { 579 int numberMatches = 0; 580 final String lcPattern = pattern.toLowerCase(); 581 packageMatches.clear(); 582 TreePath firstPath = null; 583 int firstRow = Integer.MAX_VALUE; 584 for (final Entry<Class<?>, Boolean> entry : classMatches.entrySet()) { 585 final String className = entry.getKey().getSimpleName(); 586 boolean matches = false; 587 boolean needsUpdate = false; 588 if ("".equals(pattern)) { 589 matches = false; 590 } else { 591 matches = !pattern.equals("") && className.toLowerCase().contains(lcPattern); 592 if (!matches) { 593 final String htmlDescription = getHtmlDescription(entry.getKey()); 594 matches = htmlDescription.toLowerCase().contains(lcPattern); 595 } 596 } 597 if (entry.getValue() != matches) { 598 needsUpdate = true; 599 } 600 entry.setValue(matches); 601 logger.fine(pattern + ": " + entry.getKey() + " set to " + matches); 602 if (matches) { 603 final TreePath path = new TreePath( 604 new Object[] { root, entry.getKey().getPackage(), entry.getKey() }); 605 final int row = tree.getRowForPath(path); 606 if (row < firstRow) { 607 firstPath = path; 608 firstRow = row; 609 } 610 611 numberMatches++; 612 final Package pkg = entry.getKey().getPackage(); 613 packageMatches.put(pkg, true); 614 if (needsUpdate) { 615 fireTreeNodeChanged(pkg); 616 fireTreeNodeChanged(entry.getKey()); 617 } 618 } 619 } 620 if (firstPath == null) { 621 tree.clearSelection(); 622 } else { 623 tree.setSelectionPath(firstPath); 624 tree.scrollPathToVisible(firstPath); 625 } 626 matchCount = numberMatches; 627 return numberMatches; 628 } 629 630 @Override 631 public boolean matches(final Object value) { 632 if (value instanceof Class<?>) { 633 return classMatches.get(value); 634 } 635 final Boolean res = packageMatches.get(value); 636 logger.fine("check " + value + " results in: " + res); 637 return res == null ? false : res; 638 } 639 640 @Override 641 public boolean isLeaf(final Object node) { 642 return node instanceof Class<?>; 643 } 644 645 @Override 646 public void removeTreeModelListener(final TreeModelListener l) { 647 listeners.remove(TreeModelListener.class, l); 648 } 649 650 @Override 651 public void valueForPathChanged(final TreePath path, final Object newValue) { 652 fireTreeNodeChanged(path); 653 } 654 655 /** 656 * @return FileFilter for searching class files (no inner classes) 657 */ 658 private FileFilter getFileFilter() { 659 if (classFileFilter == null) { 660 classFileFilter = new FileFilter() { 661 662 /** 663 * @see FileFilter 664 */ 665 @Override 666 public boolean accept(final File pathname) { 667 return (pathname.isDirectory() 668 && (pathname.getName().length() == 0 || pathname.getName().charAt(0) != '.')) 669 || (pathname.getName().endsWith(".class") && pathname.getName().indexOf('$') < 0); 670 } 671 }; 672 } 673 return classFileFilter; 674 } 675 676 /** 677 * Load a class specified by a file- or entry-name 678 * 679 * @param name 680 * name of a file or entry 681 * @return class file that was denoted by the name, null if no class or does not contain a main method 682 */ 683 private Class<?> load(final String name) { 684 if (name.endsWith(".class") && name.indexOf('$') < 0) { 685 String classname = name.substring(0, name.length() - ".class".length()); 686 687 if (classname.length() > 0 && classname.charAt(0) == '/') { 688 classname = classname.substring(1); 689 } 690 classname = classname.replace('/', '.'); 691 692 try { 693 final Class<?> cls = Class.forName(classname); 694 cls.getMethod("main", new Class[] { String[].class }); 695 if (!getClass().equals(cls)) { 696 return cls; 697 } 698 } catch (final NoClassDefFoundError e) { 699 // class has unresolved dependencies 700 return null; 701 } catch (final ClassNotFoundException e) { 702 // class not in classpath 703 return null; 704 } catch (final NoSuchMethodException e) { 705 // class does not have a main method 706 return null; 707 } 708 } 709 return null; 710 } 711 712 /** 713 * Used to descent in directories, loads classes via {@link #load} 714 * 715 * @param directory 716 * where to search for class files 717 * @param packageName 718 * current package name for the diven directory 719 * @param recursive 720 * true to descent into subdirectories 721 */ 722 private void addAllFilesInDirectory(final File directory, final String packageName, final boolean recursive) { 723 // Get the list of the files contained in the package 724 logger.fine(directory + " -> " + packageName); 725 final File[] files = directory.listFiles(getFileFilter()); 726 if (files != null) { 727 for (final File file : files) { 728 // we are only interested in .class files 729 if (file.isDirectory()) { 730 if (recursive) { 731 addAllFilesInDirectory(file, packageName + "." + file.getName(), true); 732 } 733 } else { 734 final Class<?> result = load(packageName + "." + file.getName()); 735 if (result != null) { 736 addClassForPackage(result); 737 } 738 } 739 } 740 } 741 } 742 743 protected void fireTreeChanged() { 744 for (final TreeModelListener l : listeners.getListeners(TreeModelListener.class)) { 745 l.treeStructureChanged(new TreeModelEvent(this, new String[] { root })); 746 } 747 } 748 749 protected void fireTreeNodeChanged(final TreePath tp) { 750 final Object obj = tp.getLastPathComponent(); 751 final TreePath parentPath = tp.getParentPath(); 752 final Object parentObj = parentPath.getLastPathComponent(); 753 final TreeModelEvent ev = new TreeModelEvent(this, parentPath, 754 new int[] { getIndexOfChild(parentObj, obj) }, new Object[] { obj }); 755 for (final TreeModelListener l : listeners.getListeners(TreeModelListener.class)) { 756 l.treeNodesChanged(ev); 757 } 758 } 759 760 protected void fireTreeNodeChanged(final Package pkg) { 761 final TreeModelEvent ev = new TreeModelEvent(this, new Object[] { root }, 762 new int[] { getIndexOfChild(root, pkg) }, new Object[] { pkg }); 763 for (final TreeModelListener l : listeners.getListeners(TreeModelListener.class)) { 764 l.treeNodesChanged(ev); 765 } 766 } 767 768 protected void fireTreeNodeChanged(final Class<?> clazz) { 769 final Package pkg = clazz.getPackage(); 770 final TreeModelEvent ev = new TreeModelEvent(this, new Object[] { root, pkg }, 771 new int[] { getIndexOfChild(pkg, clazz) }, new Object[] { clazz }); 772 for (final TreeModelListener l : listeners.getListeners(TreeModelListener.class)) { 773 l.treeNodesChanged(ev); 774 } 775 } 776 777 public void reload(final String pckgname) { 778 root = pckgname; 779 size = 0; 780 find(pckgname, true); 781 fireTreeChanged(); 782 } 783 784 public int getSize() { 785 return size; 786 } 787 788 protected void find(final String pckgname, final boolean recursive) { 789 URL url; 790 791 // Translate the package name into an absolute path 792 String name = pckgname; 793 name = name.replace('.', '/'); 794 795 // Get a File object for the package 796 // URL url = UPBClassLoader.get().getResource(name); 797 url = ResourceLocatorTool.getClassPathResource(ExampleRunner.class, name); 798 // URL url = ClassLoader.getSystemClassLoader().getResource(name); 799 // pckgname = pckgname + "."; 800 801 File directory; 802 try { 803 directory = new File(URLDecoder.decode(url.getFile(), "UTF-8")); 804 } catch (final UnsupportedEncodingException e) { 805 throw new RuntimeException(e); // should never happen 806 } 807 808 if (directory.exists()) { 809 logger.info("Searching for examples in \"" + directory.getPath() + "\"."); 810 addAllFilesInDirectory(directory, pckgname, recursive); 811 } else { 812 try { 813 // It does not work with the filesystem: we must 814 // be in the case of a package contained in a jar file. 815 logger.info("Searching for Demo classes in \"" + url + "\"."); 816 final URLConnection urlConnection = url.openConnection(); 817 if (urlConnection instanceof JarURLConnection) { 818 final JarURLConnection conn = (JarURLConnection) urlConnection; 819 820 final JarFile jfile = conn.getJarFile(); 821 final Enumeration<JarEntry> e = jfile.entries(); 822 while (e.hasMoreElements()) { 823 final ZipEntry entry = e.nextElement(); 824 final Class<?> result = load(entry.getName()); 825 if (result != null) { 826 addClassForPackage(result); 827 } 828 } 829 } 830 } catch (final IOException e) { 831 logger.logp(Level.SEVERE, this.getClass().toString(), "find(pckgname, recursive, classes)", 832 "Exception", e); 833 } catch (final Exception e) { 834 logger.logp(Level.SEVERE, this.getClass().toString(), "find(pckgname, recursive, classes)", 835 "Exception", e); 836 } 837 } 838 } 839 840 public int getMatchCount() { 841 return matchCount; 842 } 843 } 844 845 private static class ClassNameCellRenderer implements TreeCellRenderer { 846 847 DefaultTreeCellRenderer defaultRenderer = new DefaultTreeCellRenderer(); 848 JLabel classNameLabel = new JLabel(" "); 849 850 { 851 classNameLabel.setOpaque(true); 852 } 853 Font defaultFont = classNameLabel.getFont(); 854 Font matchFont = defaultFont.deriveFont(Font.BOLD); 855 SearchFilter searchFilter; 856 857 public ClassNameCellRenderer(final SearchFilter searchFilter) { 858 this.searchFilter = searchFilter; 859 } 860 861 @Override 862 public Component getTreeCellRendererComponent(final JTree tree, final Object value, final boolean selected, 863 final boolean expanded, final boolean leaf, final int row, final boolean hasFocus) { 864 if (value == null) { 865 classNameLabel.setText("Null"); // Throw an exception? 866 classNameLabel.setFont(defaultFont); 867 } else { 868 if (value instanceof Class<?>) { 869 final Class<?> clazz = (Class<?>) value; 870 classNameLabel.setText(clazz.getSimpleName()); 871 } else if (value instanceof Package) { 872 String name = ((Package) value).getName(); 873 if (name.startsWith(tree.getModel().getRoot().toString())) { 874 name = name.substring(tree.getModel().getRoot().toString().length() + 1); 875 } 876 classNameLabel.setText(name); 877 } else { 878 classNameLabel.setText(value.toString()); 879 } 880 classNameLabel.setFont(searchFilter.matches(value) ? matchFont : defaultFont); 881 } 882 883 if (selected) { 884 classNameLabel.setBackground(defaultRenderer.getBackgroundSelectionColor()); 885 classNameLabel.setForeground(defaultRenderer.getTextSelectionColor()); 886 } else { 887 classNameLabel.setBackground(defaultRenderer.getBackgroundNonSelectionColor()); 888 classNameLabel.setForeground(defaultRenderer.getTextNonSelectionColor()); 889 } 890 classNameLabel.setEnabled(tree.isEnabled()); 891 return classNameLabel; 892 } 893 } 894 895 private static class ConsoleStreamer extends Thread { 896 897 InputStream is; 898 DisplayConsole console; 899 900 ConsoleStreamer(final InputStream is, final DisplayConsole console) { 901 this.is = is; 902 this.console = console; 903 } 904 905 @Override 906 public void run() { 907 try { 908 final InputStreamReader isr = new InputStreamReader(is); 909 final BufferedReader br = new BufferedReader(isr); 910 String line = null; 911 while ((line = br.readLine()) != null) { 912 if (console != null) { 913 console.appendLine(line); 914 } 915 } 916 } catch (final IOException ioe) { 917 ioe.printStackTrace(); 918 } 919 } 920 } 921 922 private static class DisplayConsole extends JPanel { 923 924 private static final long serialVersionUID = 1L; 925 private static final int MAX_CHARACTERS = 50000; 926 private final JTextArea textArea; 927 private final JScrollPane scrollPane; 928 929 public DisplayConsole() { 930 textArea = new JTextArea("Nothing started yet."); 931 textArea.setEditable(false); 932 textArea.setWrapStyleWord(true); 933 textArea.setLineWrap(true); 934 935 scrollPane = new JScrollPane(textArea); 936 scrollPane.setAutoscrolls(true); 937 scrollPane.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER); 938 939 setLayout(new BorderLayout()); 940 add(scrollPane, BorderLayout.CENTER); 941 } 942 943 public void started(final String className, final Process process) { 944 textArea.setText(className + ": started..."); 945 new Thread() { 946 947 @Override 948 public void run() { 949 try { 950 process.waitFor(); 951 } catch (final InterruptedException ex) { 952 } 953 } 954 }.start(); 955 } 956 957 public void appendLine(final String line) { 958 SwingUtilities.invokeLater(new Runnable() { 959 @Override 960 public void run() { 961 String content = textArea.getText() + "\n" + line; 962 if (content.length() > MAX_CHARACTERS) { 963 content = content.substring(content.length() - MAX_CHARACTERS); 964 } 965 textArea.setText(content); 966 textArea.setCaretPosition(textArea.getText().length()); 967 } 968 }); 969 } 970 } 971 972 private static class ErasableTextField extends JPanel { 973 974 private static final long serialVersionUID = 1L; 975 private final JTextField textField; 976 private final JButton btClear; 977 private final Color defaultTextBackground; 978 979 public ErasableTextField(final int len) { 980 super(new BorderLayout()); 981 textField = new JTextField(len); 982 textField.setToolTipText("Type text here to find matching examples."); 983 defaultTextBackground = textField.getBackground(); 984 btClear = new JButton(new AbstractAction() { 985 986 private static final long serialVersionUID = 1L; 987 988 { 989 putValue(Action.SHORT_DESCRIPTION, "Clear search pattern"); 990 putValue(Action.SMALL_ICON, getIcon("edit-clear-locationbar-rtl.png")); 991 } 992 993 @Override 994 public void actionPerformed(final ActionEvent e) { 995 textField.setText(""); 996 } 997 }); 998 btClear.setPreferredSize(new Dimension(20, 20)); 999 btClear.setFocusable(false); 1000 btClear.setBorder(null); 1001 // keep it from taking all the space in a toolbar 1002 setMaximumSize(new Dimension(120, 20)); 1003 add(textField); 1004 add(btClear, BorderLayout.EAST); 1005 } 1006 1007 public void setWarning(final boolean warn) { 1008 textField.setBackground(warn ? Color.yellow : defaultTextBackground); 1009 } 1010 1011 public Document getDocument() { 1012 return textField.getDocument(); 1013 } 1014 1015 public String getText() { 1016 return textField.getText(); 1017 } 1018 } 1019 1020 /** 1021 * @param args 1022 * the arguments 1023 */ 1024 public static void main(final String[] args) { 1025 try { 1026 UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); 1027 } catch (final Exception e) { 1028 } 1029 final ExampleRunner app = new ExampleRunner(); 1030 app.setIconImage(getIcon("ardor3d_white_24.png").getImage()); 1031 app.setSize(800, 400); 1032 app.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE); 1033 app.setLocationRelativeTo(null); 1034 app.setVisible(true); 1035 } 1036}