Jelajahi Sumber

Test chooser (#1591)

* Use NIO, add generics, remove Vector usage, use Threadpool

* Use jFrame to be visible in the task bar

* Simplify the filename validation

* Evaluate the file name first for performance reasons

* Use Class<?> instead of Class

* Organize imports so that java.util is not needed
Toni Helenius 3 tahun lalu
induk
melakukan
8acc565755
1 mengubah file dengan 165 tambahan dan 147 penghapusan
  1. 165 147
      jme3-examples/src/main/java/jme3test/TestChooser.java

+ 165 - 147
jme3-examples/src/main/java/jme3test/TestChooser.java

@@ -35,26 +35,36 @@ package jme3test;
 import com.jme3.app.LegacyApplication;
 import com.jme3.app.SimpleApplication;
 import com.jme3.system.JmeContext;
-import java.awt.*;
+import java.awt.BorderLayout;
+import java.awt.Dimension;
+import java.awt.FlowLayout;
+import java.awt.HeadlessException;
+import java.awt.Toolkit;
 import java.awt.event.*;
-import java.io.File;
-import java.io.FileFilter;
 import java.io.IOException;
-import java.io.UnsupportedEncodingException;
 import java.lang.reflect.Field;
 import java.lang.reflect.InvocationTargetException;
 import java.lang.reflect.Method;
-import java.net.JarURLConnection;
-import java.net.URL;
-import java.net.URLConnection;
-import java.net.URLDecoder;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.nio.file.DirectoryStream;
+import java.nio.file.FileSystem;
+import java.nio.file.FileSystems;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
 import java.util.Collection;
-import java.util.Enumeration;
-import java.util.Vector;
-import java.util.jar.JarFile;
+import java.util.Collections;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.SynchronousQueue;
+import java.util.concurrent.ThreadFactory;
+import java.util.concurrent.ThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
 import java.util.logging.Level;
 import java.util.logging.Logger;
-import java.util.zip.ZipEntry;
 import javax.swing.*;
 import javax.swing.border.EmptyBorder;
 import javax.swing.event.DocumentEvent;
@@ -67,7 +77,7 @@ import javax.swing.event.ListSelectionListener;
  * Class with a main method that displays a dialog to choose any jME demo to be
  * started.
  */
-public class TestChooser extends JDialog {
+public class TestChooser extends JFrame {
     private static final Logger logger = Logger.getLogger(TestChooser.class
             .getName());
 
@@ -76,29 +86,39 @@ public class TestChooser extends JDialog {
     /**
      * Only accessed from EDT
      */
-    private java.util.List selectedClass = null;
+    private List<Class<?>> selectedClass = null;
     private boolean showSetting = true;
 
+    private ExecutorService executorService;
+
     /**
      * Constructs a new TestChooser that is initially invisible.
      */
     public TestChooser() throws HeadlessException {
-        super((JFrame) null, "TestChooser");
+        super("TestChooser");
         /** This listener ends application when window is closed (x button on top right corner of test chooser).
          * @see issue#85 https://github.com/jMonkeyEngine/jmonkeyengine/issues/85
          */
         setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
     }
 
+    @Override
+    public void dispose() {
+        if (executorService != null) {
+            executorService.shutdown();
+        }
+
+        super.dispose();
+    }
+
     /**
      * @param classes
      *            vector that receives the found classes
      * @return classes vector, list of all the classes in a given package (must
      *         be found in classpath).
      */
-    private Vector<Class> find(String packageName, boolean recursive,
-            Vector<Class> classes) {
-        URL url;
+    private void find(String packageName, boolean recursive,
+            Set<Class<?>> classes) {
 
         // Translate the package name into an absolute path
         String name = packageName;
@@ -108,50 +128,40 @@ public class TestChooser extends JDialog {
         name = name.replace('.', '/');
 
         // Get a File object for the package
-        // URL url = UPBClassLoader.get().getResource(name);
-        url = this.getClass().getResource(name);
-        // URL url = ClassLoader.getSystemClassLoader().getResource(name);
         packageName = packageName + ".";
-
-        File directory;
+        URI uri;
+        FileSystem fileSystem = null;
         try {
-            directory = new File(URLDecoder.decode(url.getFile(), "UTF-8"));
-        } catch (UnsupportedEncodingException e) {
-            throw new RuntimeException(e); // should never happen
+            uri = this.getClass().getResource(name).toURI();
+        } catch (URISyntaxException e) {
+            throw new RuntimeException("Failed to load demo classes.", e);
         }
 
-        if (directory.exists()) {
-            logger.fine("Searching for Demo classes in \""
-                    + directory.getName() + "\".");
-            addAllFilesInDirectory(directory, classes, packageName, recursive);
-        } else {
+        // Special case if we are running from inside a JAR
+        if ("jar".equalsIgnoreCase(uri.getScheme())) {
             try {
-                // It does not work with the filesystem: we must
-                // be in the case of a package contained in a jar file.
-                logger.fine("Searching for Demo classes in \"" + url + "\".");
-                URLConnection urlConnection = url.openConnection();
-                if (urlConnection instanceof JarURLConnection) {
-                    JarURLConnection conn = (JarURLConnection) urlConnection;
-
-                    JarFile jfile = conn.getJarFile();
-                    Enumeration e = jfile.entries();
-                    while (e.hasMoreElements()) {
-                        ZipEntry entry = (ZipEntry) e.nextElement();
-                        Class result = load(entry.getName());
-                        if (result != null && !classes.contains(result)) {
-                            classes.add(result);
-                        }
-                    }
-                }
+                fileSystem = FileSystems.newFileSystem(uri, Collections.emptyMap());
             } catch (IOException e) {
-                logger.logp(Level.SEVERE, this.getClass().toString(),
-                        "find(packageName, recursive, classes)", "Exception", e);
-            } catch (Exception e) {
-                logger.logp(Level.SEVERE, this.getClass().toString(),
-                        "find(packageName, recursive, classes)", "Exception", e);
+                throw new RuntimeException("Failed to load demo classes from JAR.", e);
+            }
+        }
+
+        try {
+            Path directory = Paths.get(uri);
+            logger.log(Level.FINE, "Searching for Demo classes in \"{0}\".", directory.getFileName().toString());
+            addAllFilesInDirectory(directory, classes, packageName, recursive);
+        } catch (Exception e) {
+            logger.logp(Level.SEVERE, this.getClass().toString(),
+                    "find(pckgname, recursive, classes)", "Exception", e);
+        } finally {
+            if (fileSystem != null) {
+                try {
+                    fileSystem.close();
+                } catch (IOException e) {
+                    logger.log(Level.WARNING, "Failed to close JAR.", e);
+                }
             }
         }
-        return classes;
     }
 
     /**
@@ -163,29 +173,25 @@ public class TestChooser extends JDialog {
      *         not contain a main method
      */
     private Class load(String name) {
-        if (name.endsWith(".class")
-         && name.indexOf("Test") >= 0
-         && name.indexOf('$') < 0) {
-            String classname = name.substring(0, name.length()
-                    - ".class".length());
-
-            if (classname.startsWith("/")) {
-                classname = classname.substring(1);
-            }
-            classname = classname.replace('/', '.');
+        String classname = name.substring(0, name.length()
+                - ".class".length());
 
-            try {
-                final Class<?> cls = Class.forName(classname);
-                cls.getMethod("main", new Class[] { String[].class });
-                if (!getClass().equals(cls)) {
-                    return cls;
-                }
-            } catch (NoClassDefFoundError // class has unresolved dependencies
-                    | ClassNotFoundException // class not in classpath
-                    | NoSuchMethodException // class does not have a main method
-                    | UnsupportedClassVersionError e) { // unsupported version             
-                return null;
+        if (classname.startsWith("/")) {
+            classname = classname.substring(1);
+        }
+        classname = classname.replace('/', '.');
+
+        try {
+            final Class<?> cls = Class.forName(classname);
+            cls.getMethod("main", new Class[]{String[].class});
+            if (!getClass().equals(cls)) {
+                return cls;
             }
+        } catch (NoClassDefFoundError // class has unresolved dependencies
+                | ClassNotFoundException // class not in classpath
+                | NoSuchMethodException // class does not have a main method
+                | UnsupportedClassVersionError e) { // unsupported version
+            return null;
         }
         return null;
     }
@@ -202,25 +208,26 @@ public class TestChooser extends JDialog {
      * @param recursive
      *            true to descend into subdirectories
      */
-    private void addAllFilesInDirectory(File directory,
-            Collection<Class> allClasses, String packageName, boolean recursive) {
+    private void addAllFilesInDirectory(final Path directory,
+            final Set<Class<?>> allClasses, final String packageName, final boolean recursive) {
         // Get the list of the files contained in the package
-        File[] files = directory.listFiles(getFileFilter());
-        if (files != null) {
-            for (int i = 0; i < files.length; i++) {
+        try (DirectoryStream<Path> stream = Files.newDirectoryStream(directory, getFileFilter())) {
+            for (Path file : stream) {
+
                 // we are only interested in .class files
-                if (files[i].isDirectory()) {
+                if (Files.isDirectory(file)) {
                     if (recursive) {
-                        addAllFilesInDirectory(files[i], allClasses,
-                                packageName + files[i].getName() + ".", true);
+                        addAllFilesInDirectory(file, allClasses, packageName + file.getFileName() + ".", true);
                     }
                 } else {
-                    Class result = load(packageName + files[i].getName());
+                    Class<?> result = load(packageName + file.getFileName());
                     if (result != null && !allClasses.contains(result)) {
                         allClasses.add(result);
                     }
                 }
             }
+        } catch (IOException ex) {
+            logger.log(Level.SEVERE, "Could not search the folder!", ex);
         }
     }
 
@@ -228,77 +235,82 @@ public class TestChooser extends JDialog {
      * @return FileFilter for searching class files (no inner classes, only
      *         those with "Test" in the name)
      */
-    private FileFilter getFileFilter() {
-        return new FileFilter() {
+    private static DirectoryStream.Filter<Path> getFileFilter() {
+        return new DirectoryStream.Filter<Path>() {
+
             @Override
-            public boolean accept(File pathname) {
-                return (pathname.isDirectory() && !pathname.getName().startsWith("."))
-                        || (pathname.getName().endsWith(".class")
-                            && (pathname.getName().indexOf("Test") >= 0)
-                            && pathname.getName().indexOf('$') < 0);
+            public boolean accept(Path entry) throws IOException {
+                String fileName = entry.getFileName().toString();
+                return (fileName.endsWith(".class")
+                        && (fileName.contains("Test"))
+                        && !fileName.contains("$"))
+                        || (!fileName.startsWith(".") && Files.isDirectory(entry));
             }
         };
     }
 
-    private void startApp(final java.util.List appClass){
-        if (appClass == null){
+    private void startApp(final List<Class<?>> appClass) {
+        if (appClass == null || appClass.isEmpty()) {
             JOptionPane.showMessageDialog(rootPane,
                                           "Please select a test from the list",
-                                          "Error", 
+                                          "Error",
                                           JOptionPane.ERROR_MESSAGE);
             return;
         }
 
-            new Thread(new Runnable(){
-                @Override
-                public void run(){
-                    for (int i = 0; i < appClass.size(); i++) {
-                        Class<?> clazz = (Class)appClass.get(i);
-                        try {
-                            if (LegacyApplication.class.isAssignableFrom(clazz)) {
-                                Object app = clazz.newInstance();
-                                if (app instanceof SimpleApplication) {
-                                    final Method settingMethod = clazz.getMethod("setShowSettings", boolean.class);
-                                    settingMethod.invoke(app, showSetting);
-                                }
-                                final Method mainMethod = clazz.getMethod("start");
-                                mainMethod.invoke(app);
-                                Field contextField = LegacyApplication.class.getDeclaredField("context");
-                                contextField.setAccessible(true);
-                                JmeContext context = null; 
-                                while (context == null) {
-                                    context = (JmeContext) contextField.get(app);
-                                    Thread.sleep(100);
-                                }
-                                while (!context.isCreated()) {
-                                    Thread.sleep(100);
-                                }
-                                while (context.isCreated()) {
-                                    Thread.sleep(100);
-                                }
-                            } else {
-                                final Method mainMethod = clazz.getMethod("main", (new String[0]).getClass());
-                                mainMethod.invoke(clazz, new Object[]{new String[0]});
+        executorService.submit(getAppRunner(appClass));
+    }
+
+    private Runnable getAppRunner(final List<Class<?>> appClass) {
+        return new Runnable() {
+            @Override
+            public void run() {
+                for (Class<?> clazz : appClass) {
+                    try {
+                        if (LegacyApplication.class.isAssignableFrom(clazz)) {
+                            Object app = clazz.newInstance();
+                            if (app instanceof SimpleApplication) {
+                                final Method settingMethod = clazz.getMethod("setShowSettings", boolean.class);
+                                settingMethod.invoke(app, showSetting);
+                            }
+                            final Method mainMethod = clazz.getMethod("start");
+                            mainMethod.invoke(app);
+                            Field contextField = LegacyApplication.class.getDeclaredField("context");
+                            contextField.setAccessible(true);
+                            JmeContext context = null;
+                            while (context == null) {
+                                context = (JmeContext) contextField.get(app);
+                                Thread.sleep(100);
+                            }
+                            while (!context.isCreated()) {
+                                Thread.sleep(100);
                             }
-                            // wait for destroy
-                            System.gc();
-                        } catch (IllegalAccessException ex) {
-                            logger.log(Level.SEVERE, "Cannot access constructor: "+clazz.getName(), ex);
-                        } catch (IllegalArgumentException ex) {
-                            logger.log(Level.SEVERE, "main() had illegal argument: "+clazz.getName(), ex);
-                        } catch (InvocationTargetException ex) {
-                            logger.log(Level.SEVERE, "main() method had exception: "+clazz.getName(), ex);
-                        } catch (InstantiationException ex) {
-                            logger.log(Level.SEVERE, "Failed to create app: "+clazz.getName(), ex);
-                        } catch (NoSuchMethodException ex){
-                            logger.log(Level.SEVERE, "Test class doesn't have main method: "+clazz.getName(), ex);
-                        } catch (Exception ex) {
-                            logger.log(Level.SEVERE, "Cannot start test: "+clazz.getName(), ex);
-                            ex.printStackTrace();
+                            while (context.isCreated()) {
+                                Thread.sleep(100);
+                            }
+                        } else {
+                            final Method mainMethod = clazz.getMethod("main", (new String[0]).getClass());
+                            mainMethod.invoke(clazz, new Object[]{new String[0]});
                         }
+                        // wait for destroy
+                        System.gc();
+                    } catch (IllegalAccessException ex) {
+                        logger.log(Level.SEVERE, "Cannot access constructor: " + clazz.getName(), ex);
+                    } catch (IllegalArgumentException ex) {
+                        logger.log(Level.SEVERE, "main() had illegal argument: " + clazz.getName(), ex);
+                    } catch (InvocationTargetException ex) {
+                        logger.log(Level.SEVERE, "main() method had exception: " + clazz.getName(), ex);
+                    } catch (InstantiationException ex) {
+                        logger.log(Level.SEVERE, "Failed to create app: " + clazz.getName(), ex);
+                    } catch (NoSuchMethodException ex) {
+                        logger.log(Level.SEVERE, "Test class doesn't have main method: " + clazz.getName(), ex);
+                    } catch (Exception ex) {
+                        logger.log(Level.SEVERE, "Cannot start test: " + clazz.getName(), ex);
+                        ex.printStackTrace();
                     }
                 }
-            }).start();
+            }
+        };
     }
 
     /**
@@ -307,7 +319,7 @@ public class TestChooser extends JDialog {
      * @param classes
      *            what Classes to show in the list box
      */
-    private void setup(Vector<Class> classes) {
+    private void setup(Collection<Class<?>> classes) {
         final JPanel mainPanel = new JPanel();
         mainPanel.setLayout(new BorderLayout());
         getContentPane().setLayout(new BorderLayout());
@@ -316,8 +328,9 @@ public class TestChooser extends JDialog {
 
         final FilteredJList list = new FilteredJList();
         list.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
-        DefaultListModel<Class> model = new DefaultListModel<>();
-        for (Class c : classes) {
+        DefaultListModel<Class<?>> model = new DefaultListModel<>();
+        model.ensureCapacity(classes.size());
+        for (Class<?> c : classes) {
             model.addElement(c);
         }
         list.setModel(model);
@@ -379,7 +392,7 @@ public class TestChooser extends JDialog {
         center();
     }
 
-    private class FilteredJList extends JList {
+    private class FilteredJList extends JList<Class<?>> {
         private static final long serialVersionUID = 1L;
 
         private String filter;
@@ -454,15 +467,20 @@ public class TestChooser extends JDialog {
     }
 
     protected void start(String[] args) {
-        final Vector<Class> classes = new Vector<>();
+        executorService = new ThreadPoolExecutor(1, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue<>(), new ThreadFactory() {
+            @Override
+            public Thread newThread(Runnable r) {
+                return new Thread(r, "AppStarter");
+            }
+        });
+        final Set<Class<?>> classes = new LinkedHashSet<>();
         logger.fine("Composing Test list...");
         addDisplayedClasses(classes);
         setup(classes);
-        Class<?> cls;
         setVisible(true);
     }
 
-    protected void addDisplayedClasses(Vector<Class> classes) {
+    protected void addDisplayedClasses(Set<Class<?>> classes) {
         find("jme3test", true, classes);
     }