001/** 002 * Copyright (c) 2011, The University of Southampton and the individual contributors. 003 * All rights reserved. 004 * 005 * Redistribution and use in source and binary forms, with or without modification, 006 * are permitted provided that the following conditions are met: 007 * 008 * * Redistributions of source code must retain the above copyright notice, 009 * this list of conditions and the following disclaimer. 010 * 011 * * Redistributions in binary form must reproduce the above copyright notice, 012 * this list of conditions and the following disclaimer in the documentation 013 * and/or other materials provided with the distribution. 014 * 015 * * Neither the name of the University of Southampton nor the names of its 016 * contributors may be used to endorse or promote products derived from this 017 * software without specific prior written permission. 018 * 019 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 020 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 021 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 022 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 023 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 024 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 025 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 026 * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 027 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 028 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 029 */ 030/** 031 * 032 */ 033package org.openimaj.demos; 034 035import java.awt.Color; 036import java.awt.Component; 037import java.awt.Dimension; 038import java.awt.Font; 039import java.awt.GridBagConstraints; 040import java.awt.GridBagLayout; 041import java.awt.Insets; 042import java.awt.event.ActionEvent; 043import java.awt.event.ActionListener; 044import java.io.IOException; 045import java.io.InputStream; 046import java.io.PrintWriter; 047import java.io.StringWriter; 048import java.io.Writer; 049import java.lang.reflect.Method; 050import java.net.URL; 051import java.util.HashMap; 052import java.util.Map; 053import java.util.Set; 054import java.util.Vector; 055 056import javax.swing.DefaultListCellRenderer; 057import javax.swing.ImageIcon; 058import javax.swing.JButton; 059import javax.swing.JFrame; 060import javax.swing.JLabel; 061import javax.swing.JList; 062import javax.swing.JOptionPane; 063import javax.swing.JPanel; 064import javax.swing.JScrollPane; 065import javax.swing.JTabbedPane; 066import javax.swing.event.ChangeEvent; 067import javax.swing.event.ChangeListener; 068import javax.swing.event.ListSelectionEvent; 069import javax.swing.event.ListSelectionListener; 070 071import org.apache.commons.io.IOUtils; 072import org.openimaj.util.processes.JavaProcess; 073import org.openimaj.util.processes.ProcessException; 074import org.reflections.Reflections; 075import org.xhtmlrenderer.simple.XHTMLPanel; 076import org.xhtmlrenderer.simple.extend.XhtmlNamespaceHandler; 077 078import com.uwyn.jhighlight.renderer.JavaXhtmlRenderer; 079 080/** 081 * This class provides a means for listing and running demos that have been 082 * automatically scanned from the classpath. The class looks for types that are 083 * annotated with the {@link Demo} annotation and displays these in a list. The 084 * source code should be available as a resource to this class, so that the 085 * source can be loaded and displayed. 086 * <p> 087 * Icons for demos should be resized so that they are 32x32 pixels and they 088 * should ideally be transparent PNGs. 089 * <p> 090 * Screenshots should be PNGs and should be resized to be 250 pixels wide. 091 * 092 * @author David Dupplaw (dpd@ecs.soton.ac.uk) 093 * @created 2nd November 2011 094 */ 095public class Demos { 096 /** 097 * The location of the source code on the web. The class will look for the 098 * source code at this location if it cannot find it on disk. 099 */ 100 public final static String OPENIMAJ_SRC_URL = 101 "http://svn.code.sf.net/p/openimaj/code/trunk/demos/demos/src/main/java/"; 102 103 /** 104 * This is the display for the demo runner. 105 * 106 * @author David Dupplaw (dpd@ecs.soton.ac.uk) 107 * @created 2nd November 2011 108 */ 109 protected class DemoRunnerPanel extends JPanel { 110 /** */ 111 private static final long serialVersionUID = 1L; 112 113 private JTabbedPane demoTabs = new JTabbedPane(); 114 private Map<DemoPackage, JList<DemoObject>> demoTabMap = 115 new HashMap<DemoPackage, JList<DemoObject>>(); 116 private Map<DemoPackage, Vector<DemoObject>> demos = 117 new HashMap<DemoPackage, Vector<DemoObject>>(); 118 private JLabel demoTitle = new JLabel(); 119 private JLabel demoAuthor = new JLabel(); 120 private JLabel demoScreen = new JLabel(); 121 private JLabel demoDescription = new JLabel(); 122 private JButton demoRunButton = new JButton("Run Demo"); 123 private JButton demoSourceButton = new JButton("See Source Code"); 124 125 private DemoObject lastSelectedDemo = null; 126 127 /** 128 * Default constructor that sets up the display 129 */ 130 public DemoRunnerPanel() { 131 setLayout(new GridBagLayout()); 132 setBackground(Color.white); 133 134 final GridBagConstraints gbc = new GridBagConstraints(); 135 gbc.gridx = gbc.gridy = 0; 136 gbc.gridwidth = 1; 137 gbc.fill = GridBagConstraints.BOTH; 138 gbc.weightx = gbc.weighty = 1; 139 140 // Set up a banner with the OpenIMAJ logo 141 final JLabel openImajLogo = new JLabel(new ImageIcon( 142 Demos.class.getResource("/org/openimaj/demos/OpenIMAJ.png"))); 143 gbc.gridx = 1; 144 gbc.weighty = 0; 145 add(openImajLogo, gbc); 146 147 // Set up the list of demos down the left-hand side of the display 148 gbc.gridx = 0; 149 gbc.gridy++; 150 final int y = gbc.gridy; 151 gbc.weighty = 1; 152 gbc.insets = new Insets(8, 8, 8, 8); 153 add(demoTabs, gbc); 154 155 // Set up the panel down the right-hand side that displays 156 // information about each demo. 157 final JPanel p = new JPanel(new GridBagLayout()); 158 p.setOpaque(false); 159 p.setPreferredSize(new Dimension(250, 600)); 160 p.setMaximumSize(new Dimension(250, 6000)); 161 p.setMinimumSize(new Dimension(250, 100)); 162 gbc.weighty = 0; 163 gbc.fill = GridBagConstraints.BOTH; 164 gbc.insets = new Insets(0, 0, 0, 0); 165 p.add(demoTitle, gbc); 166 gbc.gridy++; 167 p.add(demoAuthor, gbc); 168 gbc.gridy++; 169 gbc.insets = new Insets(6, 0, 6, 0); 170 p.add(demoDescription, gbc); 171 gbc.gridy++; 172 p.add(demoScreen, gbc); 173 gbc.gridy++; 174 gbc.insets = new Insets(1, 0, 1, 0); 175 p.add(demoSourceButton, gbc); 176 gbc.gridy++; 177 p.add(demoRunButton, gbc); 178 179 // Add a padding panel to the right-hand panel, so that the 180 // text does spread out over the panel in an ugly way. 181 gbc.weighty = 1; 182 gbc.gridy++; 183 final JPanel paddingPanel = new JPanel(); 184 paddingPanel.setOpaque(false); 185 p.add(paddingPanel, gbc); 186 187 gbc.gridx++; 188 gbc.gridy = y; 189 gbc.weighty = 1; 190 gbc.weightx = 0; 191 gbc.insets = new Insets(8, 8, 8, 8); 192 add(p, gbc); 193 194 demoTabs.addChangeListener(new ChangeListener() 195 { 196 @Override 197 public void stateChanged(ChangeEvent e) 198 { 199 @SuppressWarnings("unchecked") 200 final JList<DemoObject> d = (JList<DemoObject>) ((JScrollPane) demoTabs.getSelectedComponent()) 201 .getViewport().getComponent(0); 202 if (d.getSelectedValue() != null) 203 updateDisplay(d.getSelectedValue()); 204 } 205 }); 206 207 demoTitle.setFont(new Font("Arial", Font.BOLD, 14)); 208 demoDescription.setFont(new Font("Arial", Font.PLAIN, 10)); 209 210 // When run button is pressed, run the selected demo 211 demoRunButton.addActionListener(new ActionListener() 212 { 213 @Override 214 public void actionPerformed(ActionEvent e) 215 { 216 try 217 { 218 final DemoObject obj = lastSelectedDemo; 219 if (obj != null) 220 { 221 // runDemo( obj.demoClass, obj.annotation ); 222 runDemoNewJVM(obj.demoClass, obj.annotation); 223 } 224 } 225 catch (final Exception e1) 226 { 227 e1.printStackTrace(); 228 } 229 } 230 }); 231 demoRunButton.setEnabled(false); 232 233 // When the source button is pressed, display the source of the 234 // selected demo. 235 demoSourceButton.addActionListener(new ActionListener() 236 { 237 @Override 238 public void actionPerformed(ActionEvent e) 239 { 240 try 241 { 242 final DemoObject obj = lastSelectedDemo; 243 244 // Get the resource where the source code is stored 245 final String resource = "/" + obj.demoClass.getCanonicalName(). 246 replace(".", "/") + ".java"; 247 System.out.println(resource); 248 249 // Read the source code from the resource 250 InputStream stream = Demos.class.getResourceAsStream(resource); 251 252 // If the stream is null it means the code isn't 253 // available. 254 // If that happens, we should try to read the source 255 // from 256 // the svn on openimaj.org. 257 if (stream == null) 258 { 259 final URL u = new URL(OPENIMAJ_SRC_URL + resource); 260 stream = u.openStream(); 261 } 262 263 final String source = IOUtils.toString(stream, "ISO-8859-1"); 264 // "<html><head></head><body><h1>Hello</h1></body></html>"; 265 266 stream.close(); 267 268 // Syntax highlight the source code in XHTML 269 final JavaXhtmlRenderer r = new JavaXhtmlRenderer(); 270 final String h = r.highlight(obj.demoClass.getSimpleName(), 271 source, "ISO-8859-1", false); 272 273 // Render the XHTML to an XHTML panel 274 final XHTMLPanel p = new XHTMLPanel(); 275 p.setDocumentFromString(h, resource, new XhtmlNamespaceHandler()); 276 277 // Stick the XHTMLPanel in a frame 278 final JFrame f = new JFrame(); 279 f.setSize(800, 600); 280 f.setLocationRelativeTo(null); 281 f.getContentPane().add(new JScrollPane(p)); 282 f.setVisible(true); 283 } 284 catch (final IOException e1) 285 { 286 e1.printStackTrace(); 287 } 288 } 289 }); 290 demoSourceButton.setEnabled(false); 291 } 292 293 /** 294 * Updates the information display of the demo. 295 * 296 * @param dObj 297 * The {@link DemoObject} for the selected demo 298 */ 299 private void updateDisplay(DemoObject dObj) { 300 demoTitle.setText(dObj.annotation.title()); 301 demoDescription.setText("<html><p>" + dObj.annotation.description() + "</p></html>"); 302 demoAuthor.setText("By " + dObj.annotation.author()); 303 304 if (dObj.annotation.screenshot() != null) 305 demoScreen.setIcon(new ImageIcon( 306 Demos.class.getResource(dObj.annotation.screenshot()))); 307 else 308 demoScreen.setIcon(null); 309 } 310 311 /** 312 * Add a demo to the list 313 * 314 * @param obj 315 * The object representing the demo 316 */ 317 public void addDemo(DemoObject obj) { 318 JList<DemoObject> d = demoTabMap.get(obj.pkg); 319 320 if (d == null) { 321 final JList<DemoObject> r = new JList<DemoObject>(); 322 demoTabMap.put(obj.pkg, r); 323 d = r; 324 325 // When the list is clicked upon, update the demo information 326 d.addListSelectionListener(new ListSelectionListener() 327 { 328 @Override 329 public void valueChanged(ListSelectionEvent e) 330 { 331 demoRunButton.setEnabled(true); 332 demoSourceButton.setEnabled(true); 333 updateDisplay(lastSelectedDemo = 334 r.getSelectedValue()); 335 } 336 }); 337 d.setCellRenderer(new IconListRenderer()); 338 339 demoTabs.addTab( 340 obj.pkg == null ? "Demos" : obj.pkg.title(), 341 new JScrollPane(d)); 342 } 343 344 Vector<DemoObject> dd = demos.get(obj.pkg); 345 346 if (dd == null) 347 demos.put(obj.pkg, dd = new Vector<Demos.DemoObject>()); 348 349 dd.add(obj); 350 d.removeAll(); 351 d.setListData(dd); 352 353 revalidate(); 354 } 355 } 356 357 /** 358 * Used for each demo in the list. 359 * 360 * @author David Dupplaw (dpd@ecs.soton.ac.uk) 361 * @created 2nd November 2011 362 */ 363 protected class DemoObject { 364 public Demo annotation; 365 public Class<?> demoClass; 366 public DemoPackage pkg; 367 368 public DemoObject(Class<?> c) { 369 annotation = c.getAnnotation(Demo.class); 370 demoClass = c; 371 pkg = demoClass.getPackage().getAnnotation(DemoPackage.class); 372 } 373 374 @Override 375 public String toString() { 376 return annotation.title(); 377 } 378 } 379 380 /** 381 * A list renderer that adds an icon to the label. 382 * 383 * @author David Dupplaw (dpd@ecs.soton.ac.uk) 384 * @created 3rd November 2011 385 */ 386 protected class IconListRenderer extends DefaultListCellRenderer { 387 private static final long serialVersionUID = 1L; 388 389 @Override 390 public Component getListCellRendererComponent(JList<?> list, Object value, 391 int index, boolean isSelected, boolean cellHasFocus) 392 { 393 final JLabel l = (JLabel) super.getListCellRendererComponent( 394 list, value, index, isSelected, cellHasFocus); 395 396 final DemoObject o = (DemoObject) value; 397 if (o.annotation.icon() != null) { 398 URL u = getClass().getResource(o.annotation.icon()); 399 if (u == null) 400 u = getClass().getResource("/defaults/demo.png"); 401 l.setIcon(new ImageIcon(u)); 402 } 403 404 return l; 405 } 406 } 407 408 /** The panel that will be used to display the list of dmeos */ 409 private DemoRunnerPanel panel = new DemoRunnerPanel(); 410 411 /** 412 * Default constructor 413 */ 414 public Demos() { 415 try { 416 // Get a list of the available demos 417 final Set<Class<?>> c = findDemos(); 418 419 // Add the demos to the list 420 for (final Class<?> cc : c) 421 panel.addDemo(new DemoObject(cc)); 422 423 // Show the menu 424 final JFrame f = new JFrame(); 425 f.getContentPane().add(panel); 426 f.setSize(800, 600); 427 f.setLocationRelativeTo(null); 428 f.setVisible(true); 429 f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); 430 } catch (final Exception e) { 431 e.printStackTrace(); 432 } 433 } 434 435 /** 436 * Finds the class files that have been annotated with the @Demo annotation. 437 */ 438 private Set<Class<?>> findDemos() { 439 final Reflections reflections = new Reflections("org.openimaj.demos"); 440 return reflections.getTypesAnnotatedWith(Demo.class); 441 } 442 443 /** 444 * Given a demo class file, will instantiate the demo and run its main 445 * method. 446 * 447 * @param clazz 448 * The demo class file 449 */ 450 @SuppressWarnings("unused") 451 private void runDemo(Class<?> clazz, Demo annotation) throws Exception { 452 try { 453 final Method main = clazz.getDeclaredMethod("main", String[].class); 454 System.out.println(main); 455 main.invoke(null, (Object) annotation.arguments()); 456 } catch (final Throwable t) { 457 final String msg = String.format("Unexpected problem: %s", 458 getStackTrace(t.getCause())); 459 JOptionPane.showMessageDialog(null, msg); 460 throw new Exception(t); 461 } 462 } 463 464 /** 465 * Given a demo class file, instantiate the demo and run its main method in 466 * a new JVM 467 * 468 * @param clazz 469 * The demo class file 470 */ 471 private void runDemoNewJVM(final Class<?> clazz, Demo annotation) throws Exception { 472 final String[] jvmArgs = annotation.vmArguments(); 473 final String[] appArgs = annotation.arguments(); 474 475 new Thread() { 476 @Override 477 public void run() { 478 try { 479 JavaProcess.runProcess(clazz, jvmArgs, appArgs); 480 } catch (final ProcessException e) { 481 e.printStackTrace(); 482 } 483 }; 484 }.start(); 485 } 486 487 /** 488 * Returns a string of a stack trace. 489 * 490 * @param aThrowable 491 * The throwable to get a string for 492 * @return The throwable's stack as a string 493 */ 494 private static String getStackTrace(Throwable aThrowable) { 495 final Writer result = new StringWriter(); 496 final PrintWriter printWriter = new PrintWriter(result); 497 aThrowable.printStackTrace(printWriter); 498 return result.toString().substring(0, 1024) + "..."; 499 } 500 501 /** 502 * Default main just starts the demo system. 503 * 504 * @param args 505 */ 506 public static void main(String[] args) { 507 new Demos(); 508 } 509}