/*
* Open Source Physics software is free software as described near the bottom of this code file.
*
* For additional information and documentation on Open Source Physics please see:
* <http://www.opensourcephysics.org/>
*/
package org.opensourcephysics.tools;
import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.Font;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.LineNumberReader;
import java.io.Reader;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.Locale;
import java.util.MissingResourceException;
import java.util.ResourceBundle;
import java.util.Vector;
import javax.swing.AbstractButton;
import javax.swing.DefaultListModel;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JComponent;
import javax.swing.JDialog;
import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.JList;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JSeparator;
import javax.swing.JTextArea;
import javax.swing.JTextField;
import javax.swing.ListSelectionModel;
import javax.swing.SwingConstants;
import org.opensourcephysics.display.DisplayRes;
import org.opensourcephysics.display.OSPRuntime;
/**
* Utility classes to work with Ejs at a high level
*
* @author Francisco Esquembre (http://fem.um.es)
* @version 1.0
*/
public class EjsTool {
static private final String INFO_FILE = ".Ejs.txt"; //$NON-NLS-1$
static public final String GET_MODEL_METHOD = "_getEjsModel"; //$NON-NLS-1$
static public final String GET_RESOURCES_METHOD = "_getEjsResources"; //$NON-NLS-1$
static public final String GET_APPLET_DIMENSION_METHOD = "_getEjsAppletDimension"; //$NON-NLS-1$
// ---- Localization
static private final String BUNDLE_NAME = "org.opensourcephysics.resources.tools.tools"; //$NON-NLS-1$
static private ResourceBundle res = ResourceBundle.getBundle(BUNDLE_NAME);
static public void setLocale(Locale locale) {
res = ResourceBundle.getBundle(BUNDLE_NAME, locale);
}
static public String getString(String key) {
try {
return res.getString(key);
} catch(MissingResourceException e) {
return '!'+key+'!';
}
}
// --- End of localization
/**
* Whether a class provides an Ejs model.
* @param _ejsClass Class
* @return boolean
*/
static public boolean hasEjsModel(Class<?> _ejsClass) {
try {
Class<?>[] c = {};
java.lang.reflect.Method getModelMethod = _ejsClass.getMethod(GET_MODEL_METHOD, c);
return getModelMethod!=null;
} catch(Exception _exc) {
return false;
}
}
/**
* Returns the preferred dimension for the simulation's main window.
* @param _ejsClass Class
* @return boolean
*/
static public java.awt.Dimension getEjsAppletDimension(Class<?> _ejsClass) {
try {
Class<?>[] c = {};
java.lang.reflect.Method getDimensionMethod = _ejsClass.getMethod(GET_APPLET_DIMENSION_METHOD, c);
if(getDimensionMethod==null) {
return null;
}
Object[] o = {};
return(java.awt.Dimension) getDimensionMethod.invoke(null, o);
} catch(Exception _exc) {
return null;
}
}
/**
* Runs the Ejs model corresponding to the given class.
* The model and resources required will be extracted using
* the ResourceLoader utility.
* @param _ejsClass Class
* @return boolean true if successful
*/
static public boolean runEjs(Class<?> _ejsClass) {
return runEjs(_ejsClass,null);
}
/**
* Runs the Ejs model corresponding to the given class providing a given password
* The model and resources required will be extracted using
* the ResourceLoader utility.
* @param _ejsClass Class
* @param _password String
* @return boolean true if successful
*/
@SuppressWarnings("unchecked")
static public boolean runEjs(Class<?> _ejsClass, String _password) {
try {
Class<?>[] c = {};
java.lang.reflect.Method getModelMethod = _ejsClass.getMethod(GET_MODEL_METHOD, c);
java.lang.reflect.Method getResourcesMethod = _ejsClass.getMethod(GET_RESOURCES_METHOD, c);
Object[] o = {};
String model = (String) getModelMethod.invoke(null, o);
java.util.Set<String> list;
if(getResourcesMethod!=null) {
list = (java.util.Set<String>) getResourcesMethod.invoke(null, o);
} else {
list = new java.util.HashSet<String>();
}
return doRunEjs(model, list, _ejsClass, _password);
} catch(Exception _exc) {
_exc.printStackTrace();
String[] message = new String[] {res.getString("EjsTool.EjsNotRunning"), res.getString("EjsTool.NoModel")+" "+_ejsClass.getName()}; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
JOptionPane.showMessageDialog((JFrame) null, message, res.getString("EjsTool.Error"), JOptionPane.WARNING_MESSAGE); //$NON-NLS-1$
return false;
}
}
/**
* To be used by EJS version 3.47 and earlier only.
* It is here to make sure that the information saved matches the
* information read by runEjs().
* @param _release String
*/
static public void saveInformation(String _home, String _release) {
try {
String filename = System.getProperty("user.home").replace('\\', '/'); //$NON-NLS-1$
if(!filename.endsWith("/")) { //$NON-NLS-1$
filename = filename+"/"; //$NON-NLS-1$
}
filename = filename+INFO_FILE;
String dir = System.getProperty("user.dir"); //$NON-NLS-1$
FileWriter fout = new FileWriter(filename);
fout.write("directory = "+dir+"\n"); //$NON-NLS-1$ //$NON-NLS-2$
fout.write("home = "+_home+"\n"); //$NON-NLS-1$ //$NON-NLS-2$
fout.write("version = "+_release+"\n"); //$NON-NLS-1$ //$NON-NLS-2$
fout.close();
} catch(Exception exc) {
exc.printStackTrace();
}
}
/**
* To be used by EJS version 4.0 and later only.
* It is here to make sure that the information saved matches the
* information read by runEjs().
* @param _binDirectoryPath String The binary directory from which EJS was launched
* @param _sourceDirectoryPath String The source directory path
* @param _release String The EJS release version
*/
static public void saveInformation(String _binDirectoryPath, String _sourceDirectoryPath, String _release) {
try {
String filename = System.getProperty("user.home").replace('\\', '/'); //$NON-NLS-1$
if(!filename.endsWith("/")) { //$NON-NLS-1$
filename = filename+"/"; //$NON-NLS-1$
}
FileWriter fout = new FileWriter(filename+INFO_FILE);
fout.write("ejs_root_directory = "+_binDirectoryPath+"\n"); //$NON-NLS-1$ //$NON-NLS-2$
//fout.write("console_options = "+_consoleOptionsFilePath+"\n");
fout.write("source_directory = "+_sourceDirectoryPath+"\n"); //$NON-NLS-1$ //$NON-NLS-2$
fout.write("version = "+_release+"\n"); //$NON-NLS-1$ //$NON-NLS-2$
fout.close();
} catch(Exception exc) {
exc.printStackTrace();
}
}
//------------------------------------
// Private methods and inner classes
//------------------------------------
/**
* Gets the path of a file in standard form.
* If it is a directory, the path ends in "/"
*/
static public String getPath(File _file) {
String path;
try {
path = _file.getCanonicalPath();
} catch(Exception exc) {
path = _file.getAbsolutePath();
}
if(org.opensourcephysics.display.OSPRuntime.isWindows()) {
path = path.replace('\\', '/');
// Sometimes the system provides c:, sometimes C:\
int a = path.indexOf(':');
if(a>0) {
path = path.substring(0, a).toUpperCase()+path.substring(a);
}
}
if(_file.isDirectory()&&!path.endsWith("/")) { //$NON-NLS-1$
path = path+"/"; //$NON-NLS-1$
}
return path;
}
/**
* Extracts an EJS model (and its resource files) from the given
* source and then runs EJS with that model. The example is extracted
* in the source directory of the users' EJS. The user will be warned
* before overwriting any file.
* @return boolean
*/
static private boolean doRunEjs(String _model, java.util.Set<String> _resources, Class<?> _ejsClass, final String _password) {
String ejsRootDirPath = null;
//String console_options = null;
String sourceDirPath = null;
String version = null;
File ejsRootDirectory = null;
java.awt.Component parentComponent = null;
try {
String filename = System.getProperty("user.home").replace('\\', '/'); //$NON-NLS-1$
if(!filename.endsWith("/")) { //$NON-NLS-1$
filename = filename+"/"; //$NON-NLS-1$
}
Reader reader = new FileReader(filename+INFO_FILE);
LineNumberReader l = new LineNumberReader(reader);
String sl = l.readLine();
while(sl!=null) {
if(sl.startsWith("ejs_root_directory = ")) { //$NON-NLS-1$
ejsRootDirPath = sl.substring("ejs_root_directory = ".length()).trim(); //$NON-NLS-1$
//else if (sl.startsWith("console_options = ")) console_options = sl.substring("console_options = ".length()).trim();
} else if(sl.startsWith("source_directory = ")) { //$NON-NLS-1$
sourceDirPath = sl.substring("source_directory = ".length()).trim(); //$NON-NLS-1$
} else if(sl.startsWith("version = ")) { //$NON-NLS-1$
version = sl.substring("version = ".length()).trim(); //$NON-NLS-1$
}
sl = l.readLine();
}
reader.close();
int major = 3;
if(version!=null) {
int index = version.indexOf('.');
if(index>=0) {
major = Integer.parseInt(version.substring(0, index));
}
}
if(major<4) { // Incorrect version, update to 4.0
JOptionPane.showMessageDialog(parentComponent, version+" "+res.getString("EjsTool.IncorrectVersion"), //$NON-NLS-1$ //$NON-NLS-2$
res.getString("EjsTool.Error"), JOptionPane.ERROR_MESSAGE); //$NON-NLS-1$
return false;
}
// See if EjsConsole.jar is there
ejsRootDirectory = new File(ejsRootDirPath);
if(!new File(ejsRootDirectory, "EjsConsole.jar").exists()) { //$NON-NLS-1$
ejsRootDirectory = null;
}
} catch(Exception exc) {
exc.printStackTrace();
ejsRootDirectory = null;
}
if(ejsRootDirectory==null) {
// Create a chooser
JFileChooser chooser = OSPRuntime.createChooser("", new String[] {}); //$NON-NLS-1$
chooser.setDialogTitle(res.getString("EjsTool.EjsNotFound")); //$NON-NLS-1$
chooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
chooser.setMultiSelectionEnabled(false);
// The message
JTextArea textArea = new JTextArea(res.getString("EjsTool.IndicateRootDir")); //$NON-NLS-1$
textArea.setWrapStyleWord(true);
textArea.setLineWrap(true);
textArea.setEditable(false);
textArea.setFont(textArea.getFont().deriveFont(java.awt.Font.BOLD));
textArea.setPreferredSize(new java.awt.Dimension(150, 60));
textArea.setBackground(chooser.getBackground());
textArea.setBorder(new javax.swing.border.EmptyBorder(5, 10, 0, 0));
chooser.setAccessory(textArea);
// Check that it exists or ask the user for it
while(ejsRootDirectory==null) {
if(chooser.showOpenDialog((java.awt.Component) null)!=JFileChooser.APPROVE_OPTION) {
return false; // The user canceled
}
ejsRootDirectory = chooser.getSelectedFile();
if(ejsRootDirectory==null) {
return false; // The user canceled
}
if(!new File(ejsRootDirectory, "EjsConsole.jar").exists()) { //$NON-NLS-1$
ejsRootDirectory = null;
}
}
}
File sourceDir = new File(sourceDirPath);
if(!sourceDir.exists()) {
sourceDir.mkdirs();
}
// Extract the model and auxiliary files
java.util.List<String> extractList = new ArrayList<String>();
// Make relative files relative
String modelPath = _model;
int modelPathLength = 0;
int index = modelPath.lastIndexOf('/');
if(index>=0) {
_model = "./"+modelPath.substring(index+1); //$NON-NLS-1$
modelPath = modelPath.substring(0, index+1); // including the '/'
modelPathLength = modelPath.length();
}
if(!_resources.contains(_model)) {
_resources.add(_model); // Make sure the model is there
}
if(modelPathLength>0) {
for(String res : _resources) {
extractList.add(res.startsWith(modelPath) ? "./"+res.substring(modelPathLength) : res); //$NON-NLS-1$
}
} else {
extractList.addAll(_resources);
}
java.util.Collections.sort(extractList);
// Auxiliary panel for the confirmation list
JPanel auxPanel = new JPanel(new BorderLayout());
JCheckBox originalPathBox = new JCheckBox(res.getString("EjsTool.KeepOriginalPath"), false); //$NON-NLS-1$
JTextField originalPathField = new JTextField(modelPath);
originalPathField.setEditable(false);
JPanel originalPathPanel = new JPanel(new BorderLayout());
originalPathPanel.add(originalPathBox,BorderLayout.WEST);
originalPathPanel.add(originalPathField,BorderLayout.CENTER);
JCheckBox quitCheckBox = null;
if (!OSPRuntime.appletMode) { // Applets do not quit
quitCheckBox = new JCheckBox(res.getString("EjsTool.QuitSimulation"), true); //$NON-NLS-1$
auxPanel.add(quitCheckBox, BorderLayout.NORTH);
}
auxPanel.add(originalPathPanel, BorderLayout.CENTER);
java.util.List<Object> finalList = ejsConfirmList(parentComponent, new java.awt.Dimension(400, 400), res.getString("EjsTool.ExtractingFiles"), res.getString("EjsTool.Message"), extractList, auxPanel); //$NON-NLS-1$ //$NON-NLS-2$
if(finalList==null) {
return false; // The user canceled
}
/* Add the "files" directory to the ResourceLoader
String filesDir=null;
if (ResourceLoader.getResource(_model)==null) { // Search in the class "files" directory
filesDir = _ejsClass.getName().replace('.', '/') + "/files";
ResourceLoader.addSearchPath(filesDir);
}
*/
File destinationDirectory = null;
String relativeDir = ""; //$NON-NLS-1$
if (originalPathBox.isSelected()) {
destinationDirectory = new File(sourceDir,modelPath);
relativeDir = modelPath;
}
else { // the user selects a destination directory
// Create a chooser
JFileChooser chooser = OSPRuntime.createChooser("", new String[] {}); //$NON-NLS-1$
chooser.setDialogTitle(res.getString("EjsTool.ChooseDestinationDirectory")); //$NON-NLS-1$
chooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
chooser.setMultiSelectionEnabled(false);
chooser.setCurrentDirectory(sourceDir);
// Choose a correct destination directory
sourceDirPath = EjsTool.getPath(sourceDir);
while(destinationDirectory==null) {
if(chooser.showOpenDialog((java.awt.Component) null)!=JFileChooser.APPROVE_OPTION) {
return false; // The user canceled
}
destinationDirectory = chooser.getSelectedFile();
if(destinationDirectory==null) {
return false; // The user canceled
}
String destDirPath = EjsTool.getPath(destinationDirectory);
if(!destDirPath.startsWith(sourceDirPath)) {
JOptionPane.showMessageDialog(parentComponent, res.getString("EjsTool.MustBeUnderSource"), //$NON-NLS-1$
res.getString("EjsTool.Error"), JOptionPane.ERROR_MESSAGE); //$NON-NLS-1$
destinationDirectory = null;
} else {
relativeDir = destDirPath.substring(sourceDirPath.length());
}
}
}
// Extract files
destinationDirectory.mkdirs();
int policy = JarTool.NO;
for(Iterator<?> it = finalList.iterator(); it.hasNext(); ) {
String resource = (String) it.next();
File targetFile = resource.startsWith("./") ? new File(destinationDirectory, resource.substring(2)) : new File(sourceDir, resource); //$NON-NLS-1$
if(targetFile.exists()) {
switch(policy) {
case JarTool.NO_TO_ALL :
continue;
case JarTool.YES_TO_ALL :
break; // will overwrite
default :
switch(policy = JarTool.confirmOverwrite(resource)) {
case JarTool.NO_TO_ALL :
case JarTool.NO :
continue;
default : // Do nothing, i.e., will overwrite the file
}
}
}
String originalName = resource.startsWith("./") ? modelPath+resource.substring(2) : resource; //$NON-NLS-1$
// System.err.println ("Extract "+originalName+" into "+targetFile.getAbsolutePath());
File result = JarTool.extract(originalName, targetFile); // Use the ResourceLoader
if(result==null) {
String[] message = new String[] {res.getString("JarTool.FileNotExtracted"), //$NON-NLS-1$
originalName+" "+res.getString("JarTool.FileNotExtractedFrom")+" "+_ejsClass.toString()}; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
JOptionPane.showMessageDialog((JFrame) null, message, res.getString("JarTool.Error"), JOptionPane.WARNING_MESSAGE); //$NON-NLS-1$
return false;
}
}
// Now run the EJS console
//final String theOptions = console_options;
final String theModel = relativeDir+(_model.startsWith("./") ? _model.substring(2) : _model); //$NON-NLS-1$
final File theDir = ejsRootDirectory;
Runnable runner = new Runnable() {
public void run() {
try {
final Vector<String> cmd = new Vector<String>();
String javaHome = System.getProperty("java.home"); //$NON-NLS-1$
if(javaHome!=null) {
cmd.add(javaHome+java.io.File.separator+"bin"+java.io.File.separator+"java"); //$NON-NLS-1$ //$NON-NLS-2$
} else {
cmd.add("java"); //$NON-NLS-1$
}
//if (theOptions!=null) cmd.add("-Dejs.console_options="+theOptions);
cmd.add("-jar"); //$NON-NLS-1$
cmd.add("EjsConsole.jar"); //$NON-NLS-1$
if (_password!=null && _password.length()>0) {
cmd.add("-launcher.password"); //$NON-NLS-1$
cmd.add("\""+_password+"\""); //$NON-NLS-1$ //$NON-NLS-2$
}
cmd.add("-file"); //$NON-NLS-1$
cmd.add(theModel);
String[] cmdarray = cmd.toArray(new String[0]);
// for (int i=0; i<cmdarray.length; i++) System.out.println ("Trying to run ["+i+"] = "+cmdarray[i]);
Process proc = Runtime.getRuntime().exec(cmdarray, null, theDir);
proc.waitFor();
} catch(Exception exc) {
exc.printStackTrace();
}
}
};
java.lang.Thread thread = new Thread(runner);
thread.setPriority(Thread.NORM_PRIORITY);
thread.start();
return quitCheckBox==null ? false : quitCheckBox.isSelected();
}
public static java.util.List<Object> ejsConfirmList(Component _target, Dimension _size, String _message, String _title, java.util.List<?> _list) {
return ejsConfirmList(_target, _size, _message, _title, _list, (JComponent) null);
}
/**
* This method receives a list of objects which it exposes to the user.
* The user can remove objects from the list and click OK.
* The class then returns the modified list.
* If the user clicks cancel it will return null
* @param _target Component The dialog will be shown relative to this component
* @param _size Dimension The size of the display dialog
* @param _message String The message to display
* @param _title String The title for the display dialog
* @param _list java.util.List<?> The initial list of objects
* @param _bottomComponent JComponent and additional component to show at the bottom
* @return AbstractList
*/
public static java.util.List<Object> ejsConfirmList(Component _target, Dimension _size, String _message, String _title, java.util.List<?> _list, JComponent _bottomComponent) {
class ReturnValue {
boolean value = false;
}
final ReturnValue returnValue = new ReturnValue();
final DefaultListModel listModel = new DefaultListModel();
for(int i = 0, n = _list.size(); i<n; i++) {
listModel.addElement(_list.get(i));
}
final JList list = new JList(listModel);
list.setEnabled(true);
list.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
list.setSelectionInterval(0, listModel.getSize()-1);
JScrollPane scrollPane = new JScrollPane(list);
scrollPane.setPreferredSize(_size);
final JDialog dialog = new JDialog();
java.awt.event.MouseAdapter mouseListener = new java.awt.event.MouseAdapter() {
public void mousePressed(java.awt.event.MouseEvent evt) {
AbstractButton button = (AbstractButton) (evt.getSource());
String aCmd = button.getActionCommand();
if(aCmd.equals("ok")) { //$NON-NLS-1$
returnValue.value = true;
dialog.setVisible(false);
} else if(aCmd.equals("cancel")) { //$NON-NLS-1$
returnValue.value = false;
dialog.setVisible(false);
} else if(aCmd.equals("selectall")) { //$NON-NLS-1$
list.setSelectionInterval(0, listModel.getSize()-1);
} else if(aCmd.equals("selectnone")) { //$NON-NLS-1$
list.removeSelectionInterval(0, listModel.getSize()-1);
}
}
};
JButton okButton = new JButton(DisplayRes.getString("GUIUtils.Ok")); //$NON-NLS-1$
okButton.setActionCommand("ok"); //$NON-NLS-1$
okButton.addMouseListener(mouseListener);
JButton cancelButton = new JButton(DisplayRes.getString("GUIUtils.Cancel")); //$NON-NLS-1$
cancelButton.setActionCommand("cancel"); //$NON-NLS-1$
cancelButton.addMouseListener(mouseListener);
JButton excludeButton = new JButton(DisplayRes.getString("GUIUtils.SelectAll")); //$NON-NLS-1$
excludeButton.setActionCommand("selectall"); //$NON-NLS-1$
excludeButton.addMouseListener(mouseListener);
JButton includeButton = new JButton(DisplayRes.getString("GUIUtils.SelectNone")); //$NON-NLS-1$
includeButton.setActionCommand("selectnone"); //$NON-NLS-1$
includeButton.addMouseListener(mouseListener);
JPanel buttonPanel = new JPanel(new FlowLayout(FlowLayout.CENTER));
buttonPanel.add(okButton);
buttonPanel.add(excludeButton);
buttonPanel.add(includeButton);
buttonPanel.add(cancelButton);
JPanel topPanel = new JPanel(new BorderLayout());
JTextArea textArea = new JTextArea(_message);
textArea.setWrapStyleWord(true);
textArea.setLineWrap(true);
textArea.setEditable(false);
textArea.setFont(textArea.getFont().deriveFont(Font.BOLD));
textArea.setBackground(topPanel.getBackground());
textArea.setBorder(new javax.swing.border.EmptyBorder(5, 5, 10, 5));
topPanel.setBorder(new javax.swing.border.EmptyBorder(5, 10, 5, 10));
topPanel.add(textArea, BorderLayout.NORTH);
topPanel.add(scrollPane, BorderLayout.CENTER);
if(_bottomComponent!=null) {
topPanel.add(_bottomComponent, BorderLayout.SOUTH);
}
JSeparator sep1 = new JSeparator(SwingConstants.HORIZONTAL);
JPanel southPanel = new JPanel(new java.awt.BorderLayout());
southPanel.add(sep1, java.awt.BorderLayout.NORTH);
southPanel.add(buttonPanel, java.awt.BorderLayout.SOUTH);
dialog.getContentPane().setLayout(new java.awt.BorderLayout(5, 0));
dialog.getContentPane().add(topPanel, java.awt.BorderLayout.CENTER);
dialog.getContentPane().add(southPanel, java.awt.BorderLayout.SOUTH);
dialog.addWindowListener(new java.awt.event.WindowAdapter() {
public void windowClosing(java.awt.event.WindowEvent event) {
returnValue.value = false;
}
});
// dialog.setSize (_size);
dialog.validate();
dialog.pack();
dialog.setTitle(_title);
dialog.setLocationRelativeTo(_target);
dialog.setModal(true);
dialog.setVisible(true);
if(!returnValue.value) {
return null;
}
@SuppressWarnings("deprecation")
Object[] selection = list.getSelectedValues();
java.util.List<Object> newList = new ArrayList<Object>();
for(int i = 0, n = selection.length; i<n; i++) {
newList.add(selection[i]);
}
return newList;
}
} // End of class
/*
* Open Source Physics software is free software; you can redistribute
* it and/or modify it under the terms of the GNU General Public License (GPL) as
* published by the Free Software Foundation; either version 2 of the License,
* or(at your option) any later version.
*
* Code that uses any portion of the code in the org.opensourcephysics package
* or any subpackage (subdirectory) of this package must must also be be released
* under the GNU GPL license.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston MA 02111-1307 USA
* or view the license online at http://www.gnu.org/copyleft/gpl.html
*
* Copyright (c) 2007 The Open Source Physics project
* http://www.opensourcephysics.org
*/