Permalink
Fetching contributors…
Cannot retrieve contributors at this time
505 lines (462 sloc) 18.8 KB
/*
* Copyright (c) 2009-2012 jMonkeyEngine
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* * Neither the name of 'jMonkeyEngine' nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package jme3test;
import com.jme3.app.LegacyApplication;
import com.jme3.app.SimpleApplication;
import com.jme3.system.JmeContext;
import java.awt.*;
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.util.Collection;
import java.util.Enumeration;
import java.util.Vector;
import java.util.jar.JarFile;
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;
import javax.swing.event.DocumentListener;
import javax.swing.event.ListSelectionEvent;
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 {
private static final Logger logger = Logger.getLogger(TestChooser.class
.getName());
private static final long serialVersionUID = 1L;
/**
* Only accessed from EDT
*/
private java.util.List selectedClass = null;
private boolean showSetting = true;
/**
* Constructs a new TestChooser that is initially invisible.
*/
public TestChooser() throws HeadlessException {
super((JFrame) null, "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);
}
/**
* @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).
*/
protected Vector<Class> find(String pckgname, boolean recursive,
Vector<Class> classes) {
URL url;
// Translate the package name into an absolute path
String name = pckgname;
if (!name.startsWith("/")) {
name = "/" + name;
}
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);
pckgname = pckgname + ".";
File directory;
try {
directory = new File(URLDecoder.decode(url.getFile(), "UTF-8"));
} catch (UnsupportedEncodingException e) {
throw new RuntimeException(e); // should never happen
}
if (directory.exists()) {
logger.fine("Searching for Demo classes in \""
+ directory.getName() + "\".");
addAllFilesInDirectory(directory, classes, pckgname, recursive);
} else {
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);
}
}
}
} catch (IOException e) {
logger.logp(Level.SEVERE, this.getClass().toString(),
"find(pckgname, recursive, classes)", "Exception", e);
} catch (Exception e) {
logger.logp(Level.SEVERE, this.getClass().toString(),
"find(pckgname, recursive, classes)", "Exception", e);
}
}
return classes;
}
/**
* Load a class specified by a file- or entry-name
*
* @param name
* name of a file or entry
* @return class file that was denoted by the name, null if no class or does
* 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('/', '.');
try {
final Class<?> cls = Class.forName(classname);
cls.getMethod("main", new Class[] { String[].class });
if (!getClass().equals(cls)) {
return cls;
}
} catch (NoClassDefFoundError e) {
// class has unresolved dependencies
return null;
} catch (ClassNotFoundException e) {
// class not in classpath
return null;
} catch (NoSuchMethodException e) {
// class does not have a main method
return null;
} catch (UnsupportedClassVersionError e){
// unsupported version
return null;
}
}
return null;
}
/**
* Used to descent in directories, loads classes via {@link #load}
*
* @param directory
* where to search for class files
* @param allClasses
* add loaded classes to this collection
* @param packageName
* current package name for the diven directory
* @param recursive
* true to descent into subdirectories
*/
private void addAllFilesInDirectory(File directory,
Collection<Class> allClasses, String packageName, 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++) {
// we are only interested in .class files
if (files[i].isDirectory()) {
if (recursive) {
addAllFilesInDirectory(files[i], allClasses,
packageName + files[i].getName() + ".", true);
}
} else {
Class result = load(packageName + files[i].getName());
if (result != null && !allClasses.contains(result)) {
allClasses.add(result);
}
}
}
}
}
/**
* @return FileFilter for searching class files (no inner classes, only
* those with "Test" in the name)
*/
private FileFilter getFileFilter() {
return new FileFilter() {
public boolean accept(File pathname) {
return (pathname.isDirectory() && !pathname.getName().startsWith("."))
|| (pathname.getName().endsWith(".class")
&& (pathname.getName().indexOf("Test") >= 0)
&& pathname.getName().indexOf('$') < 0);
}
};
}
private void startApp(final java.util.List appClass){
if (appClass == null){
JOptionPane.showMessageDialog(rootPane,
"Please select a test from the list",
"Error",
JOptionPane.ERROR_MESSAGE);
return;
}
new Thread(new Runnable(){
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]});
}
// 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();
}
/**
* Code to create components and action listeners.
*
* @param classes
* what Classes to show in the list box
*/
private void setup(Vector<Class> classes) {
final JPanel mainPanel = new JPanel();
mainPanel.setLayout(new BorderLayout());
getContentPane().setLayout(new BorderLayout());
getContentPane().add(mainPanel, BorderLayout.CENTER);
mainPanel.setBorder(new EmptyBorder(10, 10, 10, 10));
final FilteredJList list = new FilteredJList();
list.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
DefaultListModel model = new DefaultListModel();
for (Class c : classes) {
model.addElement(c);
}
list.setModel(model);
mainPanel.add(createSearchPanel(list), BorderLayout.NORTH);
mainPanel.add(new JScrollPane(list), BorderLayout.CENTER);
list.getSelectionModel().addListSelectionListener(
new ListSelectionListener() {
public void valueChanged(ListSelectionEvent e) {
selectedClass = list.getSelectedValuesList();
}
});
list.addMouseListener(new MouseAdapter() {
public void mouseClicked(MouseEvent e) {
if (e.getClickCount() == 2 && selectedClass != null) {
startApp(selectedClass);
}
}
});
list.addKeyListener(new KeyAdapter() {
@Override
public void keyTyped(KeyEvent e) {
if (e.getKeyCode() == KeyEvent.VK_ENTER) {
startApp(selectedClass);
} else if (e.getKeyCode() == KeyEvent.VK_ESCAPE) {
dispose();
}
}
});
final JPanel buttonPanel = new JPanel(new FlowLayout(FlowLayout.CENTER));
mainPanel.add(buttonPanel, BorderLayout.PAGE_END);
final JButton okButton = new JButton("Ok");
okButton.setMnemonic('O');
buttonPanel.add(okButton);
getRootPane().setDefaultButton(okButton);
okButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
startApp(selectedClass);
}
});
final JButton cancelButton = new JButton("Cancel");
cancelButton.setMnemonic('C');
buttonPanel.add(cancelButton);
cancelButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
dispose();
}
});
pack();
center();
}
private class FilteredJList extends JList {
private static final long serialVersionUID = 1L;
private String filter;
private ListModel originalModel;
public void setModel(ListModel m) {
originalModel = m;
super.setModel(m);
}
private void update() {
if (filter == null || filter.length() == 0) {
super.setModel(originalModel);
}
DefaultListModel v = new DefaultListModel();
for (int i = 0; i < originalModel.getSize(); i++) {
Object o = originalModel.getElementAt(i);
String s = String.valueOf(o).toLowerCase();
if (s.contains(filter)) {
v.addElement(o);
}
}
super.setModel(v);
if (v.getSize() == 1) {
setSelectedIndex(0);
}
revalidate();
}
public String getFilter() {
return filter;
}
public void setFilter(String filter) {
this.filter = filter.toLowerCase();
update();
}
}
/**
* center the frame.
*/
private void center() {
Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
Dimension frameSize = this.getSize();
if (frameSize.height > screenSize.height) {
frameSize.height = screenSize.height;
}
if (frameSize.width > screenSize.width) {
frameSize.width = screenSize.width;
}
this.setLocation((screenSize.width - frameSize.width) / 2,
(screenSize.height - frameSize.height) / 2);
}
/**
* Start the chooser.
*
* @param args
* command line parameters
*/
public static void main(final String[] args) {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (Exception e) {
}
new TestChooser().start(args);
}
protected void start(String[] args) {
final Vector<Class> classes = new Vector<Class>();
logger.fine("Composing Test list...");
addDisplayedClasses(classes);
setup(classes);
Class<?> cls;
setVisible(true);
}
protected void addDisplayedClasses(Vector<Class> classes) {
find("jme3test", true, classes);
}
private JPanel createSearchPanel(final FilteredJList classes) {
JPanel search = new JPanel();
search.setLayout(new BorderLayout());
search.add(new JLabel("Choose a Demo to start: Find: "),
BorderLayout.WEST);
final javax.swing.JTextField jtf = new javax.swing.JTextField();
jtf.getDocument().addDocumentListener(new DocumentListener() {
public void removeUpdate(DocumentEvent e) {
classes.setFilter(jtf.getText());
}
public void insertUpdate(DocumentEvent e) {
classes.setFilter(jtf.getText());
}
public void changedUpdate(DocumentEvent e) {
classes.setFilter(jtf.getText());
}
});
jtf.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
selectedClass = classes.getSelectedValuesList();
startApp(selectedClass);
}
});
final JCheckBox showSettingCheck = new JCheckBox("Show Setting");
showSettingCheck.setSelected(true);
showSettingCheck.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
showSetting = showSettingCheck.isSelected();
}
});
jtf.setPreferredSize(new Dimension(100, 25));
search.add(jtf, BorderLayout.CENTER);
search.add(showSettingCheck, BorderLayout.EAST);
return search;
}
}