/**
* Copyright (c) 2008-2009 Ardor Labs, Inc.
*
* This file is part of Ardor3D.
*
* Ardor3D is free software: you can redistribute it and/or modify it
* under the terms of its license which may be found in the accompanying
* LICENSE file or at <http://www.ardor3d.com/LICENSE>.
*/
package automenta.spacenet.space.video;
import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.FlowLayout;
import java.awt.event.ActionEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileFilter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.net.JarURLConnection;
import java.net.URL;
import java.net.URLConnection;
import java.net.URLDecoder;
import java.util.Enumeration;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Vector;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.zip.ZipEntry;
import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.JScrollPane;
import javax.swing.JSpinner;
import javax.swing.JSplitPane;
import javax.swing.JTextArea;
import javax.swing.JTree;
import javax.swing.ScrollPaneConstants;
import javax.swing.SpinnerNumberModel;
import javax.swing.SwingConstants;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import javax.swing.WindowConstants;
import javax.swing.event.EventListenerList;
import javax.swing.event.TreeModelEvent;
import javax.swing.event.TreeModelListener;
import javax.swing.event.TreeSelectionEvent;
import javax.swing.event.TreeSelectionListener;
import javax.swing.tree.DefaultTreeCellRenderer;
import javax.swing.tree.TreeCellRenderer;
import javax.swing.tree.TreeModel;
import javax.swing.tree.TreePath;
import com.google.common.collect.Lists;
/**
* starter for Ardor3D examples
*/
public class RunExamples extends JFrame {
private static final long serialVersionUID = 1L;
private final Logger logger = Logger.getLogger(RunExamples.class.getCanonicalName());
private final JTree tree;
private final ClassTreeModel model;
private final JSplitPane splitPane;
private final JLabel lDescription;
private final JSpinner spMemory;
private final Action runSelectedAction;
private final JCheckBox consoleBox;
private final static String HEADER = "<html><body><h2 align=\"center\">Ardor3d Examples</h2><p align=\"center\"><img src=\""
+ RunExamples.class.getResource("/com/ardor3d/example/media/images/ardor3d_white_256.jpg")
+ "\"></p></body></html>";
public RunExamples() {
setTitle("Ardor3D SDK Examples");
setLayout(new BorderLayout());
model = new ClassTreeModel();
tree = new JTree(model);
tree.setCellRenderer(new ClassNameCellRenderer());
runSelectedAction = new AbstractAction() {
private static final long serialVersionUID = 1L;
{
putValue(Action.NAME, "run");
}
public void actionPerformed(final ActionEvent e) {
runSelected();
}
};
lDescription = new JLabel();
lDescription.setVerticalTextPosition(SwingConstants.TOP);
lDescription.setVerticalAlignment(SwingConstants.TOP);
lDescription.setHorizontalAlignment(SwingConstants.CENTER);
spMemory = new JSpinner(new SpinnerNumberModel(64, 64, 8192, 1));
consoleBox = new JCheckBox("Show console: ");
consoleBox.setHorizontalTextPosition(SwingConstants.LEFT);
final JPanel pExample = new JPanel(new BorderLayout());
pExample.add(lDescription);
final JPanel pSettings = new JPanel(new FlowLayout(FlowLayout.TRAILING));
pSettings.add(consoleBox);
pSettings.add(new JLabel("Memory: "));
pSettings.add(spMemory);
pSettings.add(new JButton(runSelectedAction));
pExample.add(pSettings, BorderLayout.SOUTH);
splitPane = new JSplitPane();
splitPane.setDividerLocation(300);
splitPane.setLeftComponent(new JScrollPane(tree));
splitPane.setRightComponent(pExample);
add(splitPane);
model.reload("com.ardor3d.example");
final JPopupMenu ctxMenu = new JPopupMenu();
ctxMenu.add(runSelectedAction);
tree.setComponentPopupMenu(ctxMenu);
tree.getSelectionModel().addTreeSelectionListener(new TreeSelectionListener() {
public void valueChanged(final TreeSelectionEvent e) {
updateDescription();
}
});
tree.addMouseListener(new MouseAdapter() {
@Override
public void mouseClicked(final MouseEvent e) {
if (e.getClickCount() > 1) {
runSelected();
}
}
});
updateDescription();
}
private void updateDescription() {
Class<?> selectedClass = null;
// just take the first selected node
final TreePath tp = tree.getSelectionPath();
if (tp != null) {
final Object selected = tp.getLastPathComponent();
if (selected instanceof Class<?>) {
selectedClass = (Class<?>) selected;
}
}
if (selectedClass == null) {
lDescription.setText(HEADER);
} else {
final Purpose purpose = selectedClass.getAnnotation(Purpose.class);
String imgURL = "";
if (purpose != null) {
try {
// Look for the example's thumbnail.
final URL imageURL = RunExamples.class.getResource(purpose.thumbnailPath());
if (imageURL != null) {
imgURL = "<br><img src=\"" + imageURL + "\"/>";
}
} catch (final Exception ex) {
}
// Set our requested max heap.
spMemory.setValue(purpose.maxHeapMemory());
}
// default to Ardor3D logo if no image available.
if ("".equals(imgURL)) {
imgURL = "<img src=\""
+ RunExamples.class.getResource("/com/ardor3d/example/media/images/ardor3d_white_256.jpg")
+ "\"/>";
}
// Set the description HTML
lDescription.setText("<html><body><h2 align=\"center\">" + selectedClass.getSimpleName() + "</h2>"
+ (purpose == null ? "" : "<p>" + purpose.htmlDescription() + "</p>") + "<p align=\"center\">"
+ imgURL + "</p></body></html>");
}
}
private void runSelected() {
// just take the first selected node
final TreePath tp = tree.getSelectionPath();
if (tp == null) {
return;
}
final Object selected = tp.getLastPathComponent();
if (!(selected instanceof Class<?>)) {
return;
}
final boolean showConsole = (consoleBox.isSelected());
new Thread() {
@Override
public void run() {
try {
final Class<?> clazz = (Class<?>) selected;
final boolean isWindows = System.getProperty("os.name").contains("Windows");
final List<String> args = Lists.newArrayList();
args.add(isWindows ? "javaw" : "java");
args.add("-Xmx" + spMemory.getValue() + "M");
args.add("-cp");
args.add(System.getProperty("java.class.path"));
args.add("-Djava.library.path=" + System.getProperty("java.library.path"));
args.add(clazz.getCanonicalName());
logger.info("start " + args.toString());
final ProcessBuilder pb = new ProcessBuilder(args);
pb.redirectErrorStream(true);
final Process p = pb.start();
final InputStream in = p.getInputStream();
final DisplayConsole console = showConsole ? new DisplayConsole(clazz.getCanonicalName(), p) : null;
new ConsoleStreamer(in, console).start();
} catch (final Exception ex) {
JOptionPane.showMessageDialog(RunExamples.this, ex.toString());
}
}
}.start();
}
class ClassTreeModel implements TreeModel {
private final EventListenerList listeners = new EventListenerList();
private final LinkedHashMap<String, Vector<Class<?>>> classes = new LinkedHashMap<String, Vector<Class<?>>>();
private final String root = "all examples";
public void addTreeModelListener(final TreeModelListener l) {
listeners.add(TreeModelListener.class, l);
}
public Object getChild(final Object parent, final int index) {
if (parent == root) {
final Vector<String> vec = new Vector<String>(classes.keySet());
return vec.get(index);
}
final Vector<Class<?>> cl = classes.get(parent);
return cl == null ? null : cl.get(index);
}
public int getChildCount(final Object parent) {
if (parent == root) {
return classes.size();
}
final Vector<Class<?>> cl = classes.get(parent);
return cl == null ? 0 : cl.size();
}
public int getIndexOfChild(final Object parent, final Object child) {
if (parent == root) {
return ((List<Vector<Class<?>>>) classes.values()).indexOf(child);
}
final Vector<Class<?>> cl = classes.get(parent);
return cl == null ? 0 : cl.indexOf(child);
}
public Object getRoot() {
return root;
}
public void addClassForPackage(final String packageName, final Class<?> clazz) {
logger.fine("found " + clazz + " in " + packageName);
if (clazz.equals(RunExamples.class)) {
return;
}
Vector<Class<?>> cl = classes.get(packageName);
if (cl == null) {
cl = new Vector<Class<?>>();
classes.put(packageName, cl);
}
cl.add(clazz);
}
public boolean isLeaf(final Object node) {
return node instanceof Class<?>;
}
public void removeTreeModelListener(final TreeModelListener l) {
listeners.remove(TreeModelListener.class, l);
}
public void valueForPathChanged(final TreePath path, final Object newValue) {
; // ignored
}
/**
* @return FileFilter for searching class files (no inner classes, only those with "Test" in the name)
*/
private FileFilter getFileFilter() {
return new FileFilter() {
/**
* @see FileFilter
*/
public boolean accept(final File pathname) {
return (pathname.isDirectory() && !pathname.getName().startsWith("."))
|| (pathname.getName().endsWith(".class") && pathname.getName().indexOf('$') < 0);
}
};
}
/**
* 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(final String name) {
if (name.endsWith(".class") && 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 (final NoClassDefFoundError e) {
// class has unresolved dependencies
return null;
} catch (final ClassNotFoundException e) {
// class not in classpath
return null;
} catch (final NoSuchMethodException e) {
// class does not have a main method
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(final File directory, final String packageName, final boolean recursive) {
// Get the list of the files contained in the package
final 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], packageName + files[i].getName() + ".", true);
}
} else {
final Class<?> result = load(packageName + files[i].getName());
if (result != null) {
addClassForPackage(packageName, result);
}
}
}
}
}
protected void fireTreeChanged() {
for (final TreeModelListener l : listeners.getListeners(TreeModelListener.class)) {
l.treeStructureChanged(new TreeModelEvent(this, new String[] { root }));
}
}
public void reload(final String pckgname) {
find(pckgname, true);
fireTreeChanged();
}
protected void find(String pckgname, final boolean recursive) {
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 (final UnsupportedEncodingException e) {
throw new RuntimeException(e); // should never happen
}
if (directory.exists()) {
logger.info("Searching for examples in \"" + directory.getPath() + "\".");
addAllFilesInDirectory(directory, 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.info("Searching for Demo classes in \"" + url + "\".");
final URLConnection urlConnection = url.openConnection();
if (urlConnection instanceof JarURLConnection) {
final JarURLConnection conn = (JarURLConnection) urlConnection;
final JarFile jfile = conn.getJarFile();
final Enumeration<JarEntry> e = jfile.entries();
while (e.hasMoreElements()) {
final ZipEntry entry = e.nextElement();
final Class<?> result = load(entry.getName());
if (result != null) {
addClassForPackage(pckgname, result);
}
}
}
} catch (final IOException e) {
logger.logp(Level.SEVERE, this.getClass().toString(), "find(pckgname, recursive, classes)",
"Exception", e);
} catch (final Exception e) {
logger.logp(Level.SEVERE, this.getClass().toString(), "find(pckgname, recursive, classes)",
"Exception", e);
}
}
}
}
class ClassNameCellRenderer implements TreeCellRenderer {
DefaultTreeCellRenderer defaultRenderer = new DefaultTreeCellRenderer();
JLabel classNameLabel = new JLabel(" ");
{
classNameLabel.setOpaque(true);
}
public Component getTreeCellRendererComponent(final JTree tree, final Object value, final boolean selected,
final boolean expanded, final boolean leaf, final int row, final boolean hasFocus) {
Component returnValue = null;
if ((value != null) && (value instanceof Class<?>)) {
final Class<?> clazz = (Class<?>) value;
classNameLabel.setText(clazz.getSimpleName());
if (selected) {
classNameLabel.setBackground(defaultRenderer.getBackgroundSelectionColor());
classNameLabel.setForeground(defaultRenderer.getTextSelectionColor());
} else {
classNameLabel.setBackground(defaultRenderer.getBackgroundNonSelectionColor());
classNameLabel.setForeground(defaultRenderer.getTextNonSelectionColor());
}
classNameLabel.setEnabled(tree.isEnabled());
returnValue = classNameLabel;
}
if (returnValue == null) {
returnValue = defaultRenderer.getTreeCellRendererComponent(tree, value, selected, expanded, leaf, row,
hasFocus);
}
return returnValue;
}
}
class ConsoleStreamer extends Thread {
InputStream is;
DisplayConsole console;
ConsoleStreamer(final InputStream is, final DisplayConsole console) {
this.is = is;
this.console = console;
}
@Override
public void run() {
try {
final InputStreamReader isr = new InputStreamReader(is);
final BufferedReader br = new BufferedReader(isr);
String line = null;
while ((line = br.readLine()) != null) {
if (console != null) {
console.appendLine(line);
}
}
} catch (final IOException ioe) {
ioe.printStackTrace();
}
}
}
class DisplayConsole extends JFrame {
private static final long serialVersionUID = 1L;
final int MAX_CHARACTERS = 50000;
private final JTextArea textArea;
private final JScrollPane scrollPane;
public DisplayConsole(final String className, final Process process) {
textArea = new JTextArea(className + ": console started...");
textArea.setEditable(false);
textArea.setWrapStyleWord(true);
textArea.setLineWrap(true);
scrollPane = new JScrollPane(textArea);
scrollPane.setAutoscrolls(true);
scrollPane.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
getContentPane().setLayout(new BorderLayout());
getContentPane().add(scrollPane, BorderLayout.CENTER);
setSize(500, 300);
setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
setLocationByPlatform(true);
setVisible(true);
// Close when process ends.
new Thread() {
@Override
public void run() {
try {
process.waitFor();
} catch (final InterruptedException ex) {
}
setVisible(false);
dispose();
}
}.start();
}
public void appendLine(final String line) {
String content = textArea.getText() + "\n" + line;
if (content.length() > MAX_CHARACTERS) {
content = content.substring(content.length() - MAX_CHARACTERS);
}
textArea.setText(content);
SwingUtilities.invokeLater(new Runnable() {
public void run() {
textArea.setCaretPosition(textArea.getText().length());
}
});
}
}
/**
* @param args
*/
public static void main(final String[] args) {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (final Exception e) {
}
final RunExamples app = new RunExamples();
app.setSize(700, 400);
app.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
app.setLocationRelativeTo(null);
app.setVisible(true);
}
}