/*
* 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.ejs;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
/**
* A base interface for a simulation
*/
public abstract class Simulation implements java.lang.Runnable {
static public final int MAXIMUM_FPS = 25;
static public final int MINIMUM_FPS = 1;
private Model model = null;
private View view = null;
private java.lang.Thread thread = null;
private boolean autoplay = false, isPlaying = false;
private long delay = 0;
private java.net.URL codebase = null;
private void errorMessage(String _text) {
System.err.println(this.getClass().getName()+": "+_text); //$NON-NLS-1$
}
private void errorMessage(Exception _exc) {
System.err.println(this.getClass().getName()+": Exception caught! Text follows:"); //$NON-NLS-1$
_exc.printStackTrace(System.err);
}
// -----------------------------
// Relationship with its parts
// -----------------------------
public Model getModel() {
return model;
}
public void setModel(Model _aModel) {
model = _aModel;
}
public View getView() {
return view;
}
public void setView(View _aView) {
view = _aView;
}
/**
* Sets the codebase
*/
public void setCodebase(java.net.URL _codebase) {
codebase = _codebase;
}
/**
* Returns the codebase
*/
public java.net.URL getCodebase() {
return codebase;
}
// -----------------------------
// Controlling the execution
// -----------------------------
/**
* Sets the simulation in play mode
*/
public void play() {
if(thread!=null) {
return;
}
thread = new Thread(this);
thread.setPriority(Thread.MIN_PRIORITY);
thread.start();
isPlaying = true;
}
/**
* Stops the simulation
*/
public void pause() {
thread = null;
isPlaying = false;
}
/**
* Implementation of the Runnable interface
*/
public void run() {
while(thread==Thread.currentThread()) {
step();
try {
Thread.sleep(delay);
} catch(InterruptedException ie) {}
}
}
/**
* Sets the (approximate) number of frames per second for the simulation
* @param _fps the number of frames per second
*/
public void setFPS(int _fps) {
if(_fps<=MINIMUM_FPS) {
delay = 1000;
} else if(_fps>=MAXIMUM_FPS) {
delay = 0;
} else {
delay = (long) (1000.0/_fps);
}
}
/**
* Sets the delay between two steps of the simulation
* @param _aDelay the number of milliseconds for the delay
*/
public void setDelay(int _aDelay) {
if(_aDelay<0) {
delay = 0;
} else {
delay = _aDelay;
}
}
/**
* Sets whether the simulation should be set to play mode when it is reset.
* Default is false.
* @param _play Whether it should play
*/
public void setAutoplay(boolean _play) {
autoplay = _play;
}
/**
* Returns whether the simulation is running or not
*/
public boolean isPlaying() {
return isPlaying;
}
/**
* Returns whether the simulation is running or not
*/
public boolean isPaused() {
return !isPlaying;
}
// ------------------------------------
// Simulation logic based on the model
// ------------------------------------
/**
* Resets the simulation to a complete initial state
*/
public void reset() {
pause();
if(model!=null) {
model.reset();
model.initialize();
model.update();
}
if(view!=null) {
view.reset();
view.initialize();
view.update();
}
System.gc();
if(autoplay) {
play();
}
}
/**
* Initialize model using user interface changes
*/
public void initialize() {
if(view!=null) {
view.read();
}
if(model!=null) {
model.initialize();
model.update();
}
if(view!=null) {
// view.reset();
view.initialize();
view.update();
}
}
/**
* apply user interface changes
*/
public void apply() {
if(view!=null) {
view.read();
}
update();
}
/**
* apply user interface changes. Yes, exactly the same as apply() (I need it somewhere else :-)
*/
public void applyAll() {
// if (view!=null) Commented for optimization
view.read();
update();
}
/**
* apply a single change in the user interface
*/
public void apply(String _variable) {
if(view!=null) {
view.read(_variable);
}
// update(); // Should be called by the user
}
/**
* update
*/
public void update() {
if(model!=null) {
model.update();
}
if(view!=null) {
view.update(); // View could be null
}
}
/**
* step
*/
public void step() {
// if (model!=null) Commented for optimization
model.step();
update();
}
// --------------------------------------------------------
// Accesing model methods
// --------------------------------------------------------
public void updateAfterModelAction() {
// if (view!=null) view.initialize(); // If initialize sends all the data, this is redundant
update();
}
// --------------------------------------------------------
// Accesing model variables
// --------------------------------------------------------
static private final String dummy = ""; //$NON-NLS-1$
static private final Class<?> strClass = dummy.getClass();
/**
* This method returns a String with the value of a public variable of the
* model. If the variable is an array, individual element values are
* separated by a comma. Only public variables of primitive or String
* type can be accessed.
* @param _name The name of a public variable of the model
* @return The value of the variable as a String.
*/
public String getVariable(String _name) {
return getVariable(_name, ","); //$NON-NLS-1$
}
/**
* This method returns a String with the value of a public variable of the
* model. If the variable is an array, individual element values are
* separated by the specified separator string. Only public variables
* of primitive or String type can be accessed.
* @param _name The name of a public variable of the model
* @param _sep A separator string to use for array variables
* @return The value of the variable
*/
public String getVariable(String _name, String _sep) {
if(model==null) {
return null;
}
try {
Field field = model.getClass().getField(_name);
if(field.getType().isArray()) {
String txt = ""; //$NON-NLS-1$
Object array = field.get(model);
int l = Array.getLength(array);
for(int i = 0; i<l; i++) {
if(i>0) {
txt += _sep+Array.get(array, i).toString();
} else {
txt += Array.get(array, i).toString();
}
}
return txt;
}
return field.get(model).toString();
} catch(Exception exc) {
errorMessage(exc);
return null;
}
}
/**
* This method sets the value of a public variable of the model. If the
* variable is an array, individual element values must separated by a
* comma.
* In this case, if the number of values specifies differs with the
* length of the array (a warning may be issued and) either the extra values
* are ignored (if there are more) or the last elements of the array will be
* left unmodified (if there are less).
* If the values provided cannot be parsed to the variable type (an error
* message may be issued and) the method returns false.
* Only public variables of primitive or String type can be accessed.
* @param _name the name of a public variable of the model
* @param _value the value to be given to the variable
* @return true if the process was completed sucessfully, false otherwise
*/
public boolean setVariable(String _name, String _value) {
return setVariable(_name, _value, ","); //$NON-NLS-1$
}
/**
* This method sets the value of a public variable of the model.
* If the variable is an array, individual element values must separated by
* the specified separator string.
* In this case, if the number of values specifies differs with the
* length of the array (a warning may be issued and) either the extra values
* are ignored (if there are more) or the last elements of the array will be
* left unmodified (if there are less).
* Only public variables of primitive or String type can be accessed.
* If the values provided cannot be parsed to the variable type (an error
* message may be issued and) the method returns false.
* @param _variable the name of a public variable of the model
* @param _value the value to be given to the variable
* @param _sep the separator string for arrays
* @return true if the process was completed sucessfully, false otherwise
*/
public boolean setVariable(String _variable, String _value, String _sep) {
if(model==null) {
return false;
}
try {
Field field = model.getClass().getField(_variable);
if(field.getType().isArray()) {
boolean result = true;
Object array = field.get(model);
int i = 0, l = Array.getLength(array);
Class<?> type = field.getType().getComponentType();
java.util.StringTokenizer line = new java.util.StringTokenizer(_value, _sep);
if(l<line.countTokens()) {
errorMessage("Warning: there are less elements in the array than values provided!"); //$NON-NLS-1$
} else if(l>line.countTokens()) {
errorMessage("Warning: there are more elements in the array than values provided!"); //$NON-NLS-1$
}
while(line.hasMoreTokens()&&(i<l)) {
String token = line.nextToken();
if(type.equals(Double.TYPE)) {
Array.setDouble(array, i, Double.parseDouble(token));
} else if(type.equals(Float.TYPE)) {
Array.setFloat(array, i, Float.parseFloat(token));
} else if(type.equals(Byte.TYPE)) {
Array.setByte(array, i, Byte.parseByte(token));
} else if(type.equals(Short.TYPE)) {
Array.setShort(array, i, Short.parseShort(token));
} else if(type.equals(Integer.TYPE)) {
Array.setInt(array, i, Integer.parseInt(token));
} else if(type.equals(Long.TYPE)) {
Array.setLong(array, i, Long.parseLong(token));
} else if(type.equals(Boolean.TYPE)) {
if(token.trim().toLowerCase().equals("true")) { //$NON-NLS-1$
Array.setBoolean(array, i, true);
} else {
Array.setBoolean(array, i, false);
}
} else if(type.equals(Character.TYPE)) {
Array.setChar(array, i, token.charAt(0));
} else if(type.equals(strClass)) {
Array.set(array, i, token);
} else {
result = false;
}
i++;
}
return result;
}
Class<?> type = field.getType();
if(type.equals(Double.TYPE)) {
field.setDouble(model, Double.parseDouble(_value));
} else if(type.equals(Float.TYPE)) {
field.setFloat(model, Float.parseFloat(_value));
} else if(type.equals(Byte.TYPE)) {
field.setByte(model, Byte.parseByte(_value));
} else if(type.equals(Short.TYPE)) {
field.setShort(model, Short.parseShort(_value));
} else if(type.equals(Integer.TYPE)) {
field.setInt(model, Integer.parseInt(_value));
} else if(type.equals(Long.TYPE)) {
field.setLong(model, Long.parseLong(_value));
} else if(type.equals(Boolean.TYPE)) {
if(_value.trim().toLowerCase().equals("true")) { //$NON-NLS-1$
field.setBoolean(model, true);
} else {
field.setBoolean(model, false);
}
} else if(type.equals(Character.TYPE)) {
field.setChar(model, _value.charAt(0));
} else if(type.equals(strClass)) {
field.set(model, _value);
} else {
return false;
}
return true;
} catch(Exception exc) {
errorMessage(exc);
return false;
}
}
/**
* This method is used to set more than one variables of the model
* at once. Pairs of the type 'variable=value' must be separated
* by semicolons. Then they will be tokenized and sent to setVariable().
* @param _valueList the string containing the pairs 'variable=value'
* @return true if all the variables are correctly set by setVariable()
* @see setVariable(String,String)
*
*/
public boolean setVariables(String _valueList) {
return setVariables(_valueList, ";", ","); //$NON-NLS-1$ //$NON-NLS-2$
}
/**
* This method is used to set more than one variables of the model
* at once. Pairs of the type 'variable=value' must be separated
* by the separator string _sep. Then they will be tokenized and
* sent to setVariable(), using _arraySep as separator string for
* values of array variables.
* @param _valueList the string containing the pairs 'variable=value'
* @param _sep the separator string between pairs
* @param _arraySep the separator string for values of array variables
* @return true if all the variables are correctly set by setVariable()
* @see setVariable(String,String)
*
*/
public boolean setVariables(String _valueList, String _sep, String _arraySep) {
boolean result = true;
String name = "", value = ""; //$NON-NLS-1$ //$NON-NLS-2$
java.util.StringTokenizer line = new java.util.StringTokenizer(_valueList, _sep);
while(line.hasMoreTokens()) {
String token = line.nextToken();
int index = token.indexOf('=');
if(index<0) {
result = false;
continue;
}
name = token.substring(0, index).trim();
value = token.substring(index+1).trim();
boolean partial = setVariable(name, value, _arraySep);
if(partial==false) {
result = false;
}
}
update(); // Should this be called by the user explicitly?
return result;
}
// --------------------------------------------------------
// Input /Output
// --------------------------------------------------------
static private java.util.Hashtable<String, Object> memory = new java.util.Hashtable<String, Object>();
/**
* Saves the state of the model either to a file on the disk or to memory.
* If the name of the file starts with the prefix "ejs:", then the
* state of the model will be saved to memory, otherwise it will be
* dumped to disk.
* Security considerations apply when running the simulation as
* an applet.
* <p>
* The state of the model is saved by writing to disk all its public
* fields which implement the java.io.Serializable interface. This
* includes primitives and arrays.
* @param _filename the name of a file (either in disk or in memory)
* @return true if the file was correctly saved
*/
public boolean saveState(String _filename) {
if(model==null) {
return false;
}
try {
java.io.OutputStream out;
if(_filename.startsWith("ejs:")) { //$NON-NLS-1$
out = new java.io.ByteArrayOutputStream();
} else {
out = new java.io.FileOutputStream(_filename);
}
java.io.BufferedOutputStream bout = new java.io.BufferedOutputStream(out);
java.io.ObjectOutputStream dout = new java.io.ObjectOutputStream(bout);
java.lang.reflect.Field[] fields = model.getClass().getFields();
for(int i = 0; i<fields.length; i++) {
if(fields[i].get(model) instanceof java.io.Serializable) {
dout.writeObject(fields[i].get(model));
}
}
dout.close();
if(_filename.startsWith("ejs:")) { //$NON-NLS-1$
memory.put(_filename, ((java.io.ByteArrayOutputStream) out).toByteArray());
}
return true;
} catch(java.lang.Exception ioe) {
errorMessage("Error when trying to save"+_filename); //$NON-NLS-1$
ioe.printStackTrace(System.err);
return false;
}
}
public boolean readState(String _filename) {
return readState(_filename, null);
}
/**
* Reads the state of the model either from a file on the disk, from memory
* or from a url location.
* If the name of the file starts with the prefix "ejs:", then the
* state of the model will be read from a memory file that must have been
* created previously with the corresponding call to saveState().
* If the name of the file starts with "url:" it will be considered
* a url location and the method will attempt to read the file from this
* url (either locally or through the network).
* This file must have been previously created with a call to saveState()
* with destination a disk file, and then this file must have been
* copied at the right url location.
* dumped to disk.
* If the name of the file does not start with any of those prefixes,
* then it will be considered to be a file.
* Security considerations apply when running the simulation as
* an applet.
* <p>
* The state of the model is read by reading from disk all its public
* fields which implement the java.io.Serializable interface. This
* includes primitives and arrays.
* @param _filename the name of a file (either in disk , in memory or a url)
* @return true if the file was correctly read
*/
public boolean readState(String _filename, java.net.URL _codebase) {
if(model==null) {
return false;
}
try {
java.io.InputStream in;
// System.out.println ("filename = "+_filename);
// System.out.println ("codebase = "+_codebase);
if(_filename.startsWith("ejs:")) { //$NON-NLS-1$
in = new java.io.ByteArrayInputStream((byte[]) memory.get(_filename));
} else if(_filename.startsWith("url:")) { //$NON-NLS-1$
String url = _filename.substring(4);
// System.out.println ("url = "+url);
// System.out.println ("codebase = "+_codebase);
if((_codebase==null)||url.startsWith("http:")) { //$NON-NLS-1$
// Do nothing
} else {
url = _codebase+url;
}
// System.out.println ("URL = "+url.toString());
in = (new java.net.URL(url)).openStream();
} else {
in = new java.io.FileInputStream(_filename);
}
java.io.BufferedInputStream bin = new java.io.BufferedInputStream(in);
java.io.ObjectInputStream din = new java.io.ObjectInputStream(bin);
java.lang.reflect.Field[] fields = model.getClass().getFields();
for(int i = 0; i<fields.length; i++) {
if(fields[i].get(model) instanceof java.io.Serializable) {
fields[i].set(model, din.readObject());
}
}
din.close();
if(view!=null) {
view.initialize();
}
update();
return true;
} catch(java.lang.Exception ioe) {
errorMessage("Error when trying to read "+_filename); //$NON-NLS-1$
ioe.printStackTrace(System.err);
return false;
}
}
} // 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
*/