Home | History | Annotate | Download | only in jme3test
      1 /*
      2  * Copyright (c) 2009-2010 jMonkeyEngine
      3  * All rights reserved.
      4  *
      5  * Redistribution and use in source and binary forms, with or without
      6  * modification, are permitted provided that the following conditions are
      7  * met:
      8  *
      9  * * Redistributions of source code must retain the above copyright
     10  *   notice, this list of conditions and the following disclaimer.
     11  *
     12  * * Redistributions in binary form must reproduce the above copyright
     13  *   notice, this list of conditions and the following disclaimer in the
     14  *   documentation and/or other materials provided with the distribution.
     15  *
     16  * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
     17  *   may be used to endorse or promote products derived from this software
     18  *   without specific prior written permission.
     19  *
     20  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
     21  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
     22  * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
     23  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
     24  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
     25  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
     26  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
     27  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
     28  * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
     29  * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
     30  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     31  */
     32 
     33 package jme3test;
     34 
     35 import com.jme3.app.Application;
     36 import com.jme3.app.SimpleApplication;
     37 import com.jme3.system.JmeContext;
     38 import java.awt.*;
     39 import java.awt.event.*;
     40 import java.io.File;
     41 import java.io.FileFilter;
     42 import java.io.IOException;
     43 import java.io.UnsupportedEncodingException;
     44 import java.lang.reflect.Field;
     45 import java.lang.reflect.InvocationTargetException;
     46 import java.lang.reflect.Method;
     47 import java.net.JarURLConnection;
     48 import java.net.URL;
     49 import java.net.URLConnection;
     50 import java.net.URLDecoder;
     51 import java.util.Collection;
     52 import java.util.Enumeration;
     53 import java.util.Vector;
     54 import java.util.jar.JarFile;
     55 import java.util.logging.Level;
     56 import java.util.logging.Logger;
     57 import java.util.zip.ZipEntry;
     58 import javax.swing.*;
     59 import javax.swing.border.EmptyBorder;
     60 import javax.swing.event.DocumentEvent;
     61 import javax.swing.event.DocumentListener;
     62 import javax.swing.event.ListSelectionEvent;
     63 import javax.swing.event.ListSelectionListener;
     64 
     65 
     66 /**
     67  * Class with a main method that displays a dialog to choose any jME demo to be
     68  * started.
     69  */
     70 public class TestChooser extends JDialog {
     71     private static final Logger logger = Logger.getLogger(TestChooser.class
     72             .getName());
     73 
     74     private static final long serialVersionUID = 1L;
     75 
     76     /**
     77      * Only accessed from EDT
     78      */
     79     private Object[] selectedClass = null;
     80     private boolean showSetting = true;
     81 
     82     /**
     83      * Constructs a new TestChooser that is initially invisible.
     84      */
     85     public TestChooser() throws HeadlessException {
     86         super((JFrame) null, "TestChooser");
     87     }
     88 
     89     /**
     90      * @param classes
     91      *            vector that receives the found classes
     92      * @return classes vector, list of all the classes in a given package (must
     93      *         be found in classpath).
     94      */
     95     protected Vector<Class> find(String pckgname, boolean recursive,
     96             Vector<Class> classes) {
     97         URL url;
     98 
     99         // Translate the package name into an absolute path
    100         String name = pckgname;
    101         if (!name.startsWith("/")) {
    102             name = "/" + name;
    103         }
    104         name = name.replace('.', '/');
    105 
    106         // Get a File object for the package
    107         // URL url = UPBClassLoader.get().getResource(name);
    108         url = this.getClass().getResource(name);
    109         // URL url = ClassLoader.getSystemClassLoader().getResource(name);
    110         pckgname = pckgname + ".";
    111 
    112         File directory;
    113         try {
    114             directory = new File(URLDecoder.decode(url.getFile(), "UTF-8"));
    115         } catch (UnsupportedEncodingException e) {
    116             throw new RuntimeException(e); // should never happen
    117         }
    118 
    119         if (directory.exists()) {
    120             logger.info("Searching for Demo classes in \""
    121                     + directory.getName() + "\".");
    122             addAllFilesInDirectory(directory, classes, pckgname, recursive);
    123         } else {
    124             try {
    125                 // It does not work with the filesystem: we must
    126                 // be in the case of a package contained in a jar file.
    127                 logger.info("Searching for Demo classes in \"" + url + "\".");
    128                 URLConnection urlConnection = url.openConnection();
    129                 if (urlConnection instanceof JarURLConnection) {
    130                     JarURLConnection conn = (JarURLConnection) urlConnection;
    131 
    132                     JarFile jfile = conn.getJarFile();
    133                     Enumeration e = jfile.entries();
    134                     while (e.hasMoreElements()) {
    135                         ZipEntry entry = (ZipEntry) e.nextElement();
    136                         Class result = load(entry.getName());
    137                         if (result != null && !classes.contains(result)) {
    138                             classes.add(result);
    139                         }
    140                     }
    141                 }
    142             } catch (IOException e) {
    143                 logger.logp(Level.SEVERE, this.getClass().toString(),
    144                         "find(pckgname, recursive, classes)", "Exception", e);
    145             } catch (Exception e) {
    146                 logger.logp(Level.SEVERE, this.getClass().toString(),
    147                         "find(pckgname, recursive, classes)", "Exception", e);
    148             }
    149         }
    150         return classes;
    151     }
    152 
    153     /**
    154      * Load a class specified by a file- or entry-name
    155      *
    156      * @param name
    157      *            name of a file or entry
    158      * @return class file that was denoted by the name, null if no class or does
    159      *         not contain a main method
    160      */
    161     private Class load(String name) {
    162         if (name.endsWith(".class")
    163          && name.indexOf("Test") >= 0
    164          && name.indexOf('$') < 0) {
    165             String classname = name.substring(0, name.length()
    166                     - ".class".length());
    167 
    168             if (classname.startsWith("/")) {
    169                 classname = classname.substring(1);
    170             }
    171             classname = classname.replace('/', '.');
    172 
    173             try {
    174                 final Class<?> cls = Class.forName(classname);
    175                 cls.getMethod("main", new Class[] { String[].class });
    176                 if (!getClass().equals(cls)) {
    177                     return cls;
    178                 }
    179             } catch (NoClassDefFoundError e) {
    180                 // class has unresolved dependencies
    181                 return null;
    182             } catch (ClassNotFoundException e) {
    183                 // class not in classpath
    184                 return null;
    185             } catch (NoSuchMethodException e) {
    186                 // class does not have a main method
    187                 return null;
    188             } catch (UnsupportedClassVersionError e){
    189                 // unsupported version
    190                 return null;
    191             }
    192         }
    193         return null;
    194     }
    195 
    196     /**
    197      * Used to descent in directories, loads classes via {@link #load}
    198      *
    199      * @param directory
    200      *            where to search for class files
    201      * @param allClasses
    202      *            add loaded classes to this collection
    203      * @param packageName
    204      *            current package name for the diven directory
    205      * @param recursive
    206      *            true to descent into subdirectories
    207      */
    208     private void addAllFilesInDirectory(File directory,
    209             Collection<Class> allClasses, String packageName, boolean recursive) {
    210         // Get the list of the files contained in the package
    211         File[] files = directory.listFiles(getFileFilter());
    212         if (files != null) {
    213             for (int i = 0; i < files.length; i++) {
    214                 // we are only interested in .class files
    215                 if (files[i].isDirectory()) {
    216                     if (recursive) {
    217                         addAllFilesInDirectory(files[i], allClasses,
    218                                 packageName + files[i].getName() + ".", true);
    219                     }
    220                 } else {
    221                     Class result = load(packageName + files[i].getName());
    222                     if (result != null && !allClasses.contains(result)) {
    223                         allClasses.add(result);
    224                     }
    225                 }
    226             }
    227         }
    228     }
    229 
    230     /**
    231      * @return FileFilter for searching class files (no inner classes, only
    232      *         those with "Test" in the name)
    233      */
    234     private FileFilter getFileFilter() {
    235         return new FileFilter() {
    236             public boolean accept(File pathname) {
    237                 return (pathname.isDirectory() && !pathname.getName().startsWith("."))
    238                         || (pathname.getName().endsWith(".class")
    239                             && (pathname.getName().indexOf("Test") >= 0)
    240                             && pathname.getName().indexOf('$') < 0);
    241             }
    242         };
    243     }
    244 
    245     private void startApp(final Object[] appClass){
    246         if (appClass == null){
    247             JOptionPane.showMessageDialog(rootPane,
    248                                           "Please select a test from the list",
    249                                           "Error",
    250                                           JOptionPane.ERROR_MESSAGE);
    251             return;
    252         }
    253 
    254             new Thread(new Runnable(){
    255                 public void run(){
    256                     for (int i = 0; i < appClass.length; i++) {
    257                 	    Class<?> clazz = (Class)appClass[i];
    258                 		try {
    259                 			Object app = clazz.newInstance();
    260                 			if (app instanceof Application) {
    261                 			    if (app instanceof SimpleApplication) {
    262                 			        final Method settingMethod = clazz.getMethod("setShowSettings", boolean.class);
    263                 			        settingMethod.invoke(app, showSetting);
    264                 			    }
    265                 			    final Method mainMethod = clazz.getMethod("start");
    266                 			    mainMethod.invoke(app);
    267                 			    Field contextField = Application.class.getDeclaredField("context");
    268                 			    contextField.setAccessible(true);
    269                 			    JmeContext context = null;
    270                 			    while (context == null) {
    271                 			        context = (JmeContext) contextField.get(app);
    272                 			        Thread.sleep(100);
    273                 			    }
    274                 			    while (!context.isCreated()) {
    275                 			        Thread.sleep(100);
    276                 			    }
    277                 			    while (context.isCreated()) {
    278                 			        Thread.sleep(100);
    279                 			    }
    280                 			} else {
    281                                 final Method mainMethod = clazz.getMethod("main", (new String[0]).getClass());
    282                                 mainMethod.invoke(app, new Object[]{new String[0]});
    283                 			}
    284                 			// wait for destroy
    285                 			System.gc();
    286                 		} catch (IllegalAccessException ex) {
    287                 			logger.log(Level.SEVERE, "Cannot access constructor: "+clazz.getName(), ex);
    288                 		} catch (IllegalArgumentException ex) {
    289                 			logger.log(Level.SEVERE, "main() had illegal argument: "+clazz.getName(), ex);
    290                 		} catch (InvocationTargetException ex) {
    291                 			logger.log(Level.SEVERE, "main() method had exception: "+clazz.getName(), ex);
    292                 		} catch (InstantiationException ex) {
    293                 			logger.log(Level.SEVERE, "Failed to create app: "+clazz.getName(), ex);
    294                 		} catch (NoSuchMethodException ex){
    295                 			logger.log(Level.SEVERE, "Test class doesn't have main method: "+clazz.getName(), ex);
    296                 		} catch (Exception ex) {
    297                 		    logger.log(Level.SEVERE, "Cannot start test: "+clazz.getName(), ex);
    298                             ex.printStackTrace();
    299                         }
    300                 	}
    301                 }
    302             }).start();
    303     }
    304 
    305     /**
    306      * Code to create components and action listeners.
    307      *
    308      * @param classes
    309      *            what Classes to show in the list box
    310      */
    311     private void setup(Vector<Class> classes) {
    312         final JPanel mainPanel = new JPanel();
    313         mainPanel.setLayout(new BorderLayout());
    314         getContentPane().setLayout(new BorderLayout());
    315         getContentPane().add(mainPanel, BorderLayout.CENTER);
    316         mainPanel.setBorder(new EmptyBorder(10, 10, 10, 10));
    317 
    318         final FilteredJList list = new FilteredJList();
    319         list.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
    320         DefaultListModel model = new DefaultListModel();
    321         for (Class c : classes) {
    322             model.addElement(c);
    323         }
    324         list.setModel(model);
    325 
    326         mainPanel.add(createSearchPanel(list), BorderLayout.NORTH);
    327         mainPanel.add(new JScrollPane(list), BorderLayout.CENTER);
    328 
    329         list.getSelectionModel().addListSelectionListener(
    330                 new ListSelectionListener() {
    331                     public void valueChanged(ListSelectionEvent e) {
    332                         selectedClass = list.getSelectedValues();
    333                     }
    334                 });
    335         list.addMouseListener(new MouseAdapter() {
    336             public void mouseClicked(MouseEvent e) {
    337                 if (e.getClickCount() == 2 && selectedClass != null) {
    338                     startApp(selectedClass);
    339                 }
    340             }
    341         });
    342         list.addKeyListener(new KeyAdapter() {
    343             @Override
    344             public void keyTyped(KeyEvent e) {
    345                 if (e.getKeyCode() == KeyEvent.VK_ENTER) {
    346                     startApp(selectedClass);
    347                 } else if (e.getKeyCode() == KeyEvent.VK_ESCAPE) {
    348                     dispose();
    349                 }
    350             }
    351         });
    352 
    353         final JPanel buttonPanel = new JPanel(new FlowLayout(FlowLayout.CENTER));
    354         mainPanel.add(buttonPanel, BorderLayout.PAGE_END);
    355 
    356         final JButton okButton = new JButton("Ok");
    357         okButton.setMnemonic('O');
    358         buttonPanel.add(okButton);
    359         getRootPane().setDefaultButton(okButton);
    360         okButton.addActionListener(new ActionListener() {
    361             public void actionPerformed(ActionEvent e) {
    362                 startApp(selectedClass);
    363             }
    364         });
    365 
    366         final JButton cancelButton = new JButton("Cancel");
    367         cancelButton.setMnemonic('C');
    368         buttonPanel.add(cancelButton);
    369         cancelButton.addActionListener(new ActionListener() {
    370             public void actionPerformed(ActionEvent e) {
    371                 dispose();
    372             }
    373         });
    374 
    375         pack();
    376         center();
    377     }
    378 
    379     private class FilteredJList extends JList {
    380         private static final long serialVersionUID = 1L;
    381 
    382         private String filter;
    383         private ListModel originalModel;
    384 
    385         public void setModel(ListModel m) {
    386             originalModel = m;
    387             super.setModel(m);
    388         }
    389 
    390         private void update() {
    391             if (filter == null || filter.length() == 0) {
    392                 super.setModel(originalModel);
    393             }
    394 
    395             DefaultListModel v = new DefaultListModel();
    396             for (int i = 0; i < originalModel.getSize(); i++) {
    397                 Object o = originalModel.getElementAt(i);
    398                 String s = String.valueOf(o).toLowerCase();
    399                 if (s.contains(filter)) {
    400                     v.addElement(o);
    401                 }
    402             }
    403             super.setModel(v);
    404             if (v.getSize() == 1) {
    405                 setSelectedIndex(0);
    406             }
    407             revalidate();
    408         }
    409 
    410         public String getFilter() {
    411             return filter;
    412         }
    413 
    414         public void setFilter(String filter) {
    415             this.filter = filter.toLowerCase();
    416             update();
    417         }
    418     }
    419 
    420     /**
    421      * center the frame.
    422      */
    423     private void center() {
    424         Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
    425         Dimension frameSize = this.getSize();
    426         if (frameSize.height > screenSize.height) {
    427             frameSize.height = screenSize.height;
    428         }
    429         if (frameSize.width > screenSize.width) {
    430             frameSize.width = screenSize.width;
    431         }
    432         this.setLocation((screenSize.width - frameSize.width) / 2,
    433                 (screenSize.height - frameSize.height) / 2);
    434     }
    435 
    436     /**
    437      * Start the chooser.
    438      *
    439      * @param args
    440      *            command line parameters
    441      */
    442     public static void main(final String[] args) {
    443         try {
    444             UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
    445         } catch (Exception e) {
    446         }
    447         new TestChooser().start(args);
    448     }
    449 
    450     protected void start(String[] args) {
    451         final Vector<Class> classes = new Vector<Class>();
    452         logger.info("Composing Test list...");
    453         addDisplayedClasses(classes);
    454         setup(classes);
    455         Class<?> cls;
    456         setVisible(true);
    457     }
    458 
    459     protected void addDisplayedClasses(Vector<Class> classes) {
    460         find("jme3test", true, classes);
    461     }
    462 
    463     private JPanel createSearchPanel(final FilteredJList classes) {
    464         JPanel search = new JPanel();
    465         search.setLayout(new BorderLayout());
    466         search.add(new JLabel("Choose a Demo to start:      Find: "),
    467                 BorderLayout.WEST);
    468         final javax.swing.JTextField jtf = new javax.swing.JTextField();
    469         jtf.getDocument().addDocumentListener(new DocumentListener() {
    470             public void removeUpdate(DocumentEvent e) {
    471                 classes.setFilter(jtf.getText());
    472             }
    473 
    474             public void insertUpdate(DocumentEvent e) {
    475                 classes.setFilter(jtf.getText());
    476             }
    477 
    478             public void changedUpdate(DocumentEvent e) {
    479                 classes.setFilter(jtf.getText());
    480             }
    481         });
    482         jtf.addActionListener(new ActionListener() {
    483             public void actionPerformed(ActionEvent e) {
    484                 selectedClass = classes.getSelectedValues();
    485                 startApp(selectedClass);
    486             }
    487         });
    488         final JCheckBox showSettingCheck = new JCheckBox("Show Setting");
    489         showSettingCheck.setSelected(true);
    490         showSettingCheck.addActionListener(new ActionListener() {
    491             public void actionPerformed(ActionEvent e) {
    492                 showSetting = showSettingCheck.isSelected();
    493             }
    494         });
    495         jtf.setPreferredSize(new Dimension(100, 25));
    496         search.add(jtf, BorderLayout.CENTER);
    497         search.add(showSettingCheck, BorderLayout.EAST);
    498         return search;
    499     }
    500 }
    501