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> demoTabMap = 115 new HashMap<DemoPackage, JList>(); 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 final JList d = (JList) ((JScrollPane) demoTabs.getSelectedComponent()) 200 .getViewport().getComponent(0); 201 if (d.getSelectedValue() != null) 202 updateDisplay((DemoObject) d.getSelectedValue()); 203 } 204 }); 205 206 demoTitle.setFont(new Font("Arial", Font.BOLD, 14)); 207 demoDescription.setFont(new Font("Arial", Font.PLAIN, 10)); 208 209 // When run button is pressed, run the selected demo 210 demoRunButton.addActionListener(new ActionListener() 211 { 212 @Override 213 public void actionPerformed(ActionEvent e) 214 { 215 try 216 { 217 final DemoObject obj = lastSelectedDemo; 218 if (obj != null) 219 { 220 // runDemo( obj.demoClass, obj.annotation ); 221 runDemoNewJVM(obj.demoClass, obj.annotation); 222 } 223 } 224 catch (final Exception e1) 225 { 226 e1.printStackTrace(); 227 } 228 } 229 }); 230 demoRunButton.setEnabled(false); 231 232 // When the source button is pressed, display the source of the 233 // selected demo. 234 demoSourceButton.addActionListener(new ActionListener() 235 { 236 @Override 237 public void actionPerformed(ActionEvent e) 238 { 239 try 240 { 241 final DemoObject obj = lastSelectedDemo; 242 243 // Get the resource where the source code is stored 244 final String resource = "/" + obj.demoClass.getCanonicalName(). 245 replace(".", "/") + ".java"; 246 System.out.println(resource); 247 248 // Read the source code from the resource 249 InputStream stream = Demos.class.getResourceAsStream(resource); 250 251 // If the stream is null it means the code isn't 252 // available. 253 // If that happens, we should try to read the source 254 // from 255 // the svn on openimaj.org. 256 if (stream == null) 257 { 258 final URL u = new URL(OPENIMAJ_SRC_URL + resource); 259 stream = u.openStream(); 260 } 261 262 final String source = IOUtils.toString(stream, "ISO-8859-1"); 263 // "<html><head></head><body><h1>Hello</h1></body></html>"; 264 265 stream.close(); 266 267 // Syntax highlight the source code in XHTML 268 final JavaXhtmlRenderer r = new JavaXhtmlRenderer(); 269 final String h = r.highlight(obj.demoClass.getSimpleName(), 270 source, "ISO-8859-1", false); 271 272 // Render the XHTML to an XHTML panel 273 final XHTMLPanel p = new XHTMLPanel(); 274 p.setDocumentFromString(h, resource, new XhtmlNamespaceHandler()); 275 276 // Stick the XHTMLPanel in a frame 277 final JFrame f = new JFrame(); 278 f.setSize(800, 600); 279 f.setLocationRelativeTo(null); 280 f.getContentPane().add(new JScrollPane(p)); 281 f.setVisible(true); 282 } 283 catch (final IOException e1) 284 { 285 e1.printStackTrace(); 286 } 287 } 288 }); 289 demoSourceButton.setEnabled(false); 290 } 291 292 /** 293 * Updates the information display of the demo. 294 * 295 * @param dObj 296 * The {@link DemoObject} for the selected demo 297 */ 298 private void updateDisplay(DemoObject dObj) { 299 demoTitle.setText(dObj.annotation.title()); 300 demoDescription.setText("<html><p>" + dObj.annotation.description() + "</p></html>"); 301 demoAuthor.setText("By " + dObj.annotation.author()); 302 303 if (dObj.annotation.screenshot() != null) 304 demoScreen.setIcon(new ImageIcon( 305 Demos.class.getResource(dObj.annotation.screenshot()))); 306 else 307 demoScreen.setIcon(null); 308 } 309 310 /** 311 * Add a demo to the list 312 * 313 * @param obj 314 * The object representing the demo 315 */ 316 public void addDemo(DemoObject obj) { 317 JList d = demoTabMap.get(obj.pkg); 318 319 if (d == null) { 320 final JList r = new JList(); 321 demoTabMap.put(obj.pkg, r); 322 d = r; 323 324 // When the list is clicked upon, update the demo information 325 d.addListSelectionListener(new ListSelectionListener() 326 { 327 @Override 328 public void valueChanged(ListSelectionEvent e) 329 { 330 demoRunButton.setEnabled(true); 331 demoSourceButton.setEnabled(true); 332 updateDisplay(lastSelectedDemo = 333 (DemoObject) r.getSelectedValue()); 334 } 335 }); 336 d.setCellRenderer(new IconListRenderer()); 337 338 demoTabs.addTab( 339 obj.pkg == null ? "Demos" : obj.pkg.title(), 340 new JScrollPane(d)); 341 } 342 343 Vector<DemoObject> dd = demos.get(obj.pkg); 344 345 if (dd == null) 346 demos.put(obj.pkg, dd = new Vector<Demos.DemoObject>()); 347 348 dd.add(obj); 349 d.removeAll(); 350 d.setListData(dd); 351 352 revalidate(); 353 } 354 } 355 356 /** 357 * Used for each demo in the list. 358 * 359 * @author David Dupplaw (dpd@ecs.soton.ac.uk) 360 * @created 2nd November 2011 361 */ 362 protected class DemoObject { 363 public Demo annotation; 364 public Class<?> demoClass; 365 public DemoPackage pkg; 366 367 public DemoObject(Class<?> c) { 368 annotation = c.getAnnotation(Demo.class); 369 demoClass = c; 370 pkg = demoClass.getPackage().getAnnotation(DemoPackage.class); 371 } 372 373 @Override 374 public String toString() { 375 return annotation.title(); 376 } 377 } 378 379 /** 380 * A list renderer that adds an icon to the label. 381 * 382 * @author David Dupplaw (dpd@ecs.soton.ac.uk) 383 * @created 3rd November 2011 384 */ 385 protected class IconListRenderer extends DefaultListCellRenderer { 386 private static final long serialVersionUID = 1L; 387 388 @Override 389 public Component getListCellRendererComponent(JList list, Object value, 390 int index, boolean isSelected, boolean cellHasFocus) 391 { 392 final JLabel l = (JLabel) super.getListCellRendererComponent( 393 list, value, index, isSelected, cellHasFocus); 394 395 final DemoObject o = (DemoObject) value; 396 if (o.annotation.icon() != null) { 397 URL u = getClass().getResource(o.annotation.icon()); 398 if (u == null) 399 u = getClass().getResource("/defaults/demo.png"); 400 l.setIcon(new ImageIcon(u)); 401 } 402 403 return l; 404 } 405 } 406 407 /** The panel that will be used to display the list of dmeos */ 408 private DemoRunnerPanel panel = new DemoRunnerPanel(); 409 410 /** 411 * Default constructor 412 */ 413 public Demos() { 414 try { 415 // Get a list of the available demos 416 final Set<Class<?>> c = findDemos(); 417 418 // Add the demos to the list 419 for (final Class<?> cc : c) 420 panel.addDemo(new DemoObject(cc)); 421 422 // Show the menu 423 final JFrame f = new JFrame(); 424 f.getContentPane().add(panel); 425 f.setSize(800, 600); 426 f.setLocationRelativeTo(null); 427 f.setVisible(true); 428 f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); 429 } catch (final Exception e) { 430 e.printStackTrace(); 431 } 432 } 433 434 /** 435 * Finds the class files that have been annotated with the @Demo annotation. 436 */ 437 private Set<Class<?>> findDemos() { 438 final Reflections reflections = new Reflections("org.openimaj.demos"); 439 return reflections.getTypesAnnotatedWith(Demo.class); 440 } 441 442 /** 443 * Given a demo class file, will instantiate the demo and run its main 444 * method. 445 * 446 * @param clazz 447 * The demo class file 448 */ 449 @SuppressWarnings("unused") 450 private void runDemo(Class<?> clazz, Demo annotation) throws Exception { 451 try { 452 final Method main = clazz.getDeclaredMethod("main", String[].class); 453 System.out.println(main); 454 main.invoke(null, (Object) annotation.arguments()); 455 } catch (final Throwable t) { 456 final String msg = String.format("Unexpected problem: %s", 457 getStackTrace(t.getCause())); 458 JOptionPane.showMessageDialog(null, msg); 459 throw new Exception(t); 460 } 461 } 462 463 /** 464 * Given a demo class file, instantiate the demo and run its main method in 465 * a new JVM 466 * 467 * @param clazz 468 * The demo class file 469 */ 470 private void runDemoNewJVM(final Class<?> clazz, Demo annotation) throws Exception { 471 final String[] jvmArgs = annotation.vmArguments(); 472 final String[] appArgs = annotation.arguments(); 473 474 new Thread() { 475 @Override 476 public void run() { 477 try { 478 JavaProcess.runProcess(clazz, jvmArgs, appArgs); 479 } catch (final ProcessException e) { 480 e.printStackTrace(); 481 } 482 }; 483 }.start(); 484 } 485 486 /** 487 * Returns a string of a stack trace. 488 * 489 * @param aThrowable 490 * The throwable to get a string for 491 * @return The throwable's stack as a string 492 */ 493 private static String getStackTrace(Throwable aThrowable) { 494 final Writer result = new StringWriter(); 495 final PrintWriter printWriter = new PrintWriter(result); 496 aThrowable.printStackTrace(printWriter); 497 return result.toString().substring(0, 1024) + "..."; 498 } 499 500 /** 501 * Default main just starts the demo system. 502 * 503 * @param args 504 */ 505 public static void main(String[] args) { 506 new Demos(); 507 } 508}