/* This file is part of the source code for 3D-XplorMath-J, Version 1.0 (January 2008).
* Copyright (c) 2008 The 3D-XplorMath Consortium (http://3d-xplormath.org).
* This source code is released under a BSD License, which allows redistribution
* in source and binary form, with or without modification, provided copyright
* and license information are included, and with no warranty or guarantee of
* any kind. For details, see http://3d-xplormath.org/j/source/BSDLicense.txt
*/
package vmm.core;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Cursor;
import java.awt.GridLayout;
import java.awt.Image;
import java.awt.Point;
import java.awt.Toolkit;
import java.awt.geom.Point2D;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.StringTokenizer;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.KeyStroke;
import vmm.functions.ComplexExpression;
import vmm.functions.Expression;
import vmm.functions.ParseError;
import vmm.functions.Parser;
import vmm.functions.Variable;
/**
* Provides several static utility funtions that can be used in various places.
*/
public class Util {
private static Parser constantParser;
private static Parser constantParserWithNanAndInf;
/**
* Parses a string to produce a constant Expression. A default {@link vmm.functions.Parser} is
* used to do the parsing.
* @param expressionAsString The constant expression, represented as a string. A ParseError
* is thrown if this is not a legal real-valued expression.
* @return The expression represented by the string
*/
public static Expression parseConstantExpression(String expressionAsString) throws ParseError {
if (constantParser == null)
constantParser = new Parser();
return constantParser.parseExpression(expressionAsString);
}
/**
* Parses a string to produce a constant ComplexExpression. A default {@link vmm.functions.Parser} is
* used to do the parsing.
* @param expressionAsString The constant expression, represented as a string. A ParseError
* is thrown if this is not a legal complex expression.
* @return The expression represented by the string
*/
public static ComplexExpression parseComplexConstantExpression(String expressionAsString) throws ParseError {
if (constantParser == null)
constantParser = new Parser();
return constantParser.parseComplexExpression(expressionAsString);
}
/**
* A method that creates a cursor from an image file. The image
* file is obtained as a resource using the getResource method
* of the Toolkit class. If the resource is not found or if any
* other error occurs, then the return value of the method will
* be null. The second and third parameters specify the
* active point of the cursor -- the point that determines the
* (x,y) position of the cursor in mouse events.
*/
public static Cursor createCursorFromResource(String imageResourceFileName,
int activeX, int activeY) {
try {
Toolkit toolkit = Toolkit.getDefaultToolkit();
ClassLoader cl = MouseTask.class.getClassLoader();
java.net.URL imageURL = cl.getResource(imageResourceFileName);
if (imageURL == null) // resource not found
return null;
else {
Image image = toolkit.createImage(imageURL);
return toolkit.createCustomCursor(image,
new Point(activeX, activeY), imageResourceFileName);
// the last parameter is just a name for the cursor
}
}
catch (Exception e) {
return null;
}
}
private static char isMac = '?';
private static String commandKey = null;
/**
* Test whether the program is running on Mac OS.
*/
public static boolean isMacOS() {
if (isMac == '?') {
try {
String macTest = System.getProperty("mrj.version");
if (macTest != null)
isMac = 'Y';
else
isMac = 'N';
}
catch (Exception e) { // System.getProperty can throw a security exception
isMac = 'N';
}
}
return isMac == 'Y';
}
/**
* Returns a KeyStroke, typically for use as the accelerator for a menu command.
* Depending on the OS, either "control" or "meta" will be added to the description
* that is passed to this method. The descriptions might be just a key name, such as
* "A", or it might include modifier keys, such as "shift A" or "alt A".
*/
public static KeyStroke getAccelerator(String description) {
if (commandKey == null) {
if (isMacOS())
commandKey = "meta ";
else
commandKey = "control ";
}
return KeyStroke.getKeyStroke(commandKey + description);
}
/**
* Tries to compute the amount of memory that is available and not yet used.
*/
public static long availableMemory() {
System.gc();
Runtime runtime = Runtime.getRuntime();
long memoryInUse = runtime.totalMemory() - runtime.freeMemory(); // Looks like this can underestimate
long maxMem = runtime.maxMemory(); // amount of memory the JVM is willing to request
long mem;
if (maxMem == Long.MAX_VALUE) // This would mean that the maxMem value is useless (but it seems to work on Mac, Windows, Linux)
mem = runtime.freeMemory();
else
mem = maxMem - memoryInUse;
// System.out.println("\nFrom Util.availableMemory():");
// System.out.println(" Max Memory = " + runtime.maxMemory());
// System.out.println(" Free Memory = " + runtime.freeMemory());
// System.out.println(" Total Memory = " + runtime.totalMemory());
// System.out.println(" Avlbl Memory = " + mem);
return mem;
}
public static Point2D getPoint2DFromUser(Component parent, String prompt) {
return getPoint2DFromUser(parent, prompt, Double.NaN, Double.NaN);
}
public static Point2D getPoint2DFromUser(Component parent, String prompt, double initialX, double initialY) {
return getPoint2DFromUser(parent, prompt, initialX, initialY, "x", "y");
}
public static Point2D getPoint2DFromUser(Component parent, String prompt, double initialX, double initialY, String nameForX, String nameForY) {
RealParam xVal, yVal;
ParameterInput xIn, yIn;
xVal = new RealParam(nameForX, initialX);
yVal = new RealParam(nameForY, initialY);
xIn = new ParameterInput(xVal);
yIn = new ParameterInput(yVal);
xIn.setColumns(15);
yIn.setColumns(15);
if (Double.isNaN(initialX))
xIn.setText("");
if (Double.isNaN(initialY))
yIn.setText("");
JPanel top = new JPanel();
top.setLayout(new BorderLayout(3,3));
top.add( new JLabel(xVal.getTitle() + " = "), BorderLayout.WEST);
top.add(xIn, BorderLayout.CENTER);
JPanel bottom = new JPanel();
bottom.setLayout(new BorderLayout(3,3));
bottom.add( new JLabel(yVal.getTitle() + " = "), BorderLayout.WEST);
bottom.add(yIn, BorderLayout.CENTER);
JPanel input = new JPanel();
input.setLayout(new GridLayout(2,1,10,10));
input.add(top);
input.add(bottom);
JPanel content = new JPanel();
content.setLayout(new BorderLayout(10,10));
content.add(input,BorderLayout.CENTER);
content.add(new JLabel(prompt,JLabel.LEFT), BorderLayout.NORTH);
int response = JOptionPane.showConfirmDialog(parent,content,"",JOptionPane.OK_CANCEL_OPTION, JOptionPane.PLAIN_MESSAGE);
if (response == JOptionPane.CANCEL_OPTION)
return null;
String xStr = xIn.getText();
String yStr = yIn.getText();
double newX, newY;
try {
newX = Double.parseDouble(xStr);
newY = Double.parseDouble(yStr);
}
catch (NumberFormatException e) {
JOptionPane.showMessageDialog(parent, I18n.tr("vmm.core.Util.getPoint2D.error.illegalnumbers"));
return null;
}
return new Point2D.Double(newX,newY);
}
/**
* A convenience method that breaks up a string into tokens, where
* the tokens are substrings seprated by specified delimiters.
* For example, explode("ab,cde,f,ghij", ",") produces an array
* of the four substrings "ab" "cde" "f" "ghi".
* @param str the string that is to be exploded; if null, then the return
* value is null
* @param separators characters in this string are separatos between tokens
* in the string that is being exploded. If this is null, then the
* return value will be an array of length one containing str as its
* only element.
*/
public static String[] explode(String str, String separators) {
if (str == null)
return null;
if (separators == null)
return new String[] { str };
StringTokenizer tokenizer = new StringTokenizer(str, separators);
int ct = tokenizer.countTokens();
String[] tokens = new String[ct];
for (int i = 0; i < ct; i++)
tokens[i] = tokenizer.nextToken();
return tokens;
}
/**
* Converts any value to a string -- hopefull a string that can be parsed
* by {@link #externalStringToValue(String, Class)} to recover the original
* value. This method definitely works for: the wrapper types such as Double, Complex,
* Color, Point2D, Vector3D, double[], int[]. For other types, this method
* returns value.toString(), which might work. In no case does it throw an exception.
* Note that a null value is converted to the string "##NULL##". For double and
* float numbers, the special strings NaN, +INF, -INF, and EPSILON are used
* to represent the special values Not-a-number, positive infinity, negative
* infinity, and min_value (the smallest positive value). This method is
* used in {@link SaveAndRestore} and in other places where conversion of
* an object to a string is needed.
*/
public static String toExternalString(Object value) {
try {
if (value == null)
return "##NULL##";
else if (value instanceof Double) {
double x = ((Double)value).doubleValue();
if (Double.isNaN(x))
return "NaN";
else if (x == Double.POSITIVE_INFINITY)
return "+INF";
else if (x == Double.NEGATIVE_INFINITY)
return "-INF";
else if (x == Double.MIN_VALUE)
return "EPSILON";
else
return "" + x;
}
else if (value instanceof Float) {
float x = ((Float)value).floatValue();
if (Float.isNaN(x))
return "NaN";
else if (x == Float.POSITIVE_INFINITY)
return "+INF";
else if (x == Float.NEGATIVE_INFINITY)
return "-INF";
else if (x == Float.MIN_VALUE)
return "EPSILON";
else
return "" + x;
}
else if (value instanceof Color) {
Color c = (Color)value;
return c.getRed() + " " + c.getGreen() + " " + c.getBlue();
}
else if (value instanceof Point2D) {
Point2D v = (Point2D)value;
return toExternalString(v.getX()) + " " + toExternalString(v.getY());
}
else if (value instanceof double[]) {
String s = "";
double[] d = (double[])value;
for (int i = 0; i < d.length; i++) {
s = s + toExternalString(d[i]);
if (i < d.length-1)
s = s + " ";
}
return s;
}
else if (value instanceof int[]) {
String s = "";
int[] d = (int[])value;
for (int i = 0; i < d.length; i++) {
s = s + d[i];
if (i < d.length-1)
s = s + " ";
}
return s;
}
}
catch (Exception e) {
}
return value.toString();
}
/**
* Tries to convert a string to a value of a specified type. This method can handle
* the strings produced by {@link #toExternalString(Object)} for at least the following
* types: the wrapper types, Color, Point2D, Complex, Vector3D, double[], and int[].
* The special string "##NULL##" is converted to the value null (except in the case
* of primitive types.) The method will work for primitive types such as Double.TYPE.
* Note that for the type Double.TYPE, the return value is of type Double (just as it
* would be for Double.class). Aside from these built-in types, the method will try
* to find a static method in the class named "fromString" and with one parameter of
* type String. If such a method is found, it will be called and its return value
* will be the return value of this mthod. If no such method is found, an attempt
* is made to find a constructor in the class with one parameter of type String.
* If such a constructor is found, it is invoked to produce the return value.
*
An exception of type IllegalArgumentException will be thrown if none of this * succeeds in producing an object of the specified type. No other type of * exception will be thrown. */ public static Object externalStringToValue(String str, Class valueType) throws IllegalArgumentException { try { if (valueType.isPrimitive()) { if (valueType.equals(Boolean.TYPE)) return new Boolean(str); else if (valueType.equals(Integer.TYPE)) return new Integer(str); else if (valueType.equals(Double.TYPE)) { if ("NaN".equalsIgnoreCase(str)) return Double.NaN; else if ("+INF".equalsIgnoreCase(str)) return Double.POSITIVE_INFINITY; else if ("-INF".equalsIgnoreCase(str)) return Double.NEGATIVE_INFINITY; else if ("EPSILON".equalsIgnoreCase(str)) return Double.MIN_VALUE; else return new Double(str); } else if (valueType.equals(Float.TYPE)) { if ("NaN".equalsIgnoreCase(str)) return Float.NaN; else if ("+INF".equalsIgnoreCase(str)) return Float.POSITIVE_INFINITY; else if ("-INF".equalsIgnoreCase(str)) return Float.NEGATIVE_INFINITY; else if ("EPSILON".equalsIgnoreCase(str)) return Float.MIN_VALUE; else return new Float(str); } else if (valueType.equals(Long.TYPE)) return new Long(str); else if (valueType.equals(Short.TYPE)) return new Short(str); else if (valueType.equals(Byte.TYPE)) return new Byte(str); else return new Character(str.charAt(0)); } else if (str.equalsIgnoreCase("##NULL##")) return null; else if (valueType.equals(Double.class)) return externalStringToValue(str, Double.TYPE); else if (valueType.equals(Float.class)) return externalStringToValue(str, Float.TYPE); else if (valueType.equals(String.class)) return str; else if (valueType.equals(Complex.class)) { if (constantParserWithNanAndInf == null) { constantParserWithNanAndInf = new Parser(); constantParserWithNanAndInf.add(new Variable("NaN",Double.NaN)); constantParserWithNanAndInf.add(new Variable("INF",Double.POSITIVE_INFINITY)); } return constantParserWithNanAndInf.parseComplexExpression(str).value(); } else if (valueType.equals(Color.class)) { StringTokenizer tokenizer = new StringTokenizer(str," ,\t"); String redString = tokenizer.nextToken(); String greenString = tokenizer.nextToken(); String blueString = tokenizer.nextToken(); return new Color( Integer.parseInt(redString), Integer.parseInt(greenString), Integer.parseInt(blueString)); } else if (valueType.equals(double[].class)) { StringTokenizer tokenizer = new StringTokenizer(str,", \t"); int ct = tokenizer.countTokens(); double[] numbers = new double[ct]; for (int i = 0; i < ct; i++) { String s = tokenizer.nextToken(); numbers[i] = (Double)externalStringToValue(s, Double.TYPE); } return numbers; } else if (valueType.equals(int[].class)) { StringTokenizer t = new StringTokenizer(str," ,\t"); int[] a = new int[t.countTokens()]; for (int i = 0; i < a.length; i++) a[i] = Integer.parseInt(t.nextToken()); return a; } else if (valueType.equals(Point2D.class)) { double[] nums = (double[])externalStringToValue(str,double[].class); if (nums.length != 2) throw new Exception(); return new Point2D.Double(nums[0],nums[1]); } else { // try to make the object using either a static fromString(String) or, if there is none, // a constructor with one parameter of type String. If none of this works, an error occurs. Method fromExternalString = null; try { fromExternalString = valueType.getDeclaredMethod("fromString", new Class[] { String.class }); } catch (Exception e) { } if (fromExternalString != null) { Object obj = fromExternalString.invoke(null, str); if (valueType.isInstance(obj)) return obj; else throw new Exception(); } else { Constructor constructor = valueType.getConstructor(new Class[] { String.class }); return constructor.newInstance(new Object[] { str }); } } } catch (Exception e) { throw new IllegalArgumentException(I18n.tr("vmm.core.Util.error.BadString",str)); } } }