From: plaa Date: Tue, 22 May 2012 04:11:25 +0000 (+0000) Subject: Expression parser updates X-Git-Tag: upstream/12.09^2~252 X-Git-Url: https://git.gag.com/?a=commitdiff_plain;h=293c53455b254f8d0aef8bc616d5fcd3b695b9cc;p=debian%2Fopenrocket Expression parser updates git-svn-id: https://openrocket.svn.sourceforge.net/svnroot/openrocket/trunk@700 180e2498-e6e9-4542-8430-84ac67f01cd8 --- diff --git a/core/.classpath b/core/.classpath index 275144a9..002d4561 100644 --- a/core/.classpath +++ b/core/.classpath @@ -27,5 +27,6 @@ + diff --git a/core/build.xml b/core/build.xml index 7d93b880..af9ed8a4 100644 --- a/core/build.xml +++ b/core/build.xml @@ -86,6 +86,7 @@ + diff --git a/core/lib/exp4j-0.2.9.jar b/core/lib/exp4j-0.2.9.jar new file mode 100644 index 00000000..a1209940 Binary files /dev/null and b/core/lib/exp4j-0.2.9.jar differ diff --git a/core/src/net/sf/openrocket/gui/adaptors/DoubleModel.java b/core/src/net/sf/openrocket/gui/adaptors/DoubleModel.java index b79903cd..3216f0a7 100644 --- a/core/src/net/sf/openrocket/gui/adaptors/DoubleModel.java +++ b/core/src/net/sf/openrocket/gui/adaptors/DoubleModel.java @@ -23,16 +23,14 @@ import net.sf.openrocket.unit.Unit; import net.sf.openrocket.unit.UnitGroup; import net.sf.openrocket.util.BugException; import net.sf.openrocket.util.ChangeSource; +import net.sf.openrocket.util.ExpressionParser; +import net.sf.openrocket.util.InvalidExpressionException; import net.sf.openrocket.util.Invalidatable; import net.sf.openrocket.util.Invalidator; import net.sf.openrocket.util.MathUtil; import net.sf.openrocket.util.MemoryManagement; import net.sf.openrocket.util.Reflection; import net.sf.openrocket.util.StateChangeListener; -import net.sf.openrocket.util.exp4j.Calculable; -import net.sf.openrocket.util.exp4j.ExpressionBuilder; -import net.sf.openrocket.util.exp4j.UnknownFunctionException; -import net.sf.openrocket.util.exp4j.UnparsableExpressionException; /** @@ -51,12 +49,12 @@ import net.sf.openrocket.util.exp4j.UnparsableExpressionException; public class DoubleModel implements StateChangeListener, ChangeSource, Invalidatable { private static final LogHelper log = Application.getLogger(); - - + + public static final DoubleModel ZERO = new DoubleModel(0); - + //////////// JSpinner Model //////////// - + /** * Model suitable for JSpinner. * Note: Previously used using JSpinner.NumberEditor and extended SpinnerNumberModel @@ -65,12 +63,14 @@ public class DoubleModel implements StateChangeListener, ChangeSource, Invalidat * to be entered so that fractional units and expressions can be used. */ public class ValueSpinnerModel extends AbstractSpinnerModel implements Invalidatable { - + + private ExpressionParser parser = new ExpressionParser(); + @Override public Object getValue() { return currentUnit.toString(DoubleModel.this.getValue()); } - + @Override public void setValue(Object value) { if (firing > 0) { @@ -83,44 +83,39 @@ public class DoubleModel implements StateChangeListener, ChangeSource, Invalidat Number num = Double.NaN; // Set num if possible - if ( value instanceof Number ) { - num = (Number)value; + if (value instanceof Number) { + num = (Number) value; } - else if ( value instanceof String ) { + else if (value instanceof String) { try { - String newValString = (String)value; - ExpressionBuilder builder=new ExpressionBuilder(newValString); - Calculable calc=builder.build(); - num = calc.calculate(); + String newValString = (String) value; + num = parser.parse(newValString); + } catch (InvalidExpressionException e) { + // Ignore } - catch ( java.lang.NumberFormatException e ) { - } catch (UnknownFunctionException e) { - } catch (UnparsableExpressionException e) { - } catch (java.util.EmptyStackException e) { - } } - + // Update the doublemodel with the new number or return to the last number if not possible - if ( ((Double)num).isNaN() ) { - DoubleModel.this.setValue( lastValue ); + if (((Double) num).isNaN()) { + DoubleModel.this.setValue(lastValue); log.user("SpinnerModel could not set value for " + DoubleModel.this.toString() + ". Could not convert " + value.toString()); - } - else { + } + else { double newValue = num.doubleValue(); double converted = currentUnit.fromUnit(newValue); - + log.user("SpinnerModel setValue called for " + DoubleModel.this.toString() + " newValue=" + newValue + " converted=" + converted); DoubleModel.this.setValue(converted); } // Force a refresh if text doesn't match up exactly with the stored value - if ( ! ((Double)lastValue).toString().equals( this.getValue().toString() ) ) { + if (!((Double) lastValue).toString().equals(this.getValue().toString())) { DoubleModel.this.fireStateChanged(); - log.debug("SpinnerModel "+DoubleModel.this.toString()+" refresh forced because string did not match actual value."); + log.debug("SpinnerModel " + DoubleModel.this.toString() + " refresh forced because string did not match actual value."); } } - + @Override public Object getNextValue() { double d = currentUnit.toUnit(DoubleModel.this.getValue()); @@ -132,7 +127,7 @@ public class DoubleModel implements StateChangeListener, ChangeSource, Invalidat d = max; return d; } - + @Override public Object getPreviousValue() { double d = currentUnit.toUnit(DoubleModel.this.getValue()); @@ -144,23 +139,23 @@ public class DoubleModel implements StateChangeListener, ChangeSource, Invalidat d = min; return d; } - + @Override public void addChangeListener(ChangeListener l) { DoubleModel.this.addChangeListener(l); } - + @Override public void removeChangeListener(ChangeListener l) { DoubleModel.this.removeChangeListener(l); } - + @Override public void invalidate() { DoubleModel.this.invalidate(); } } - + /** * Returns a new SpinnerModel with the same base as the DoubleModel. * The values given to the JSpinner are in the currently selected units. @@ -170,69 +165,69 @@ public class DoubleModel implements StateChangeListener, ChangeSource, Invalidat public SpinnerModel getSpinnerModel() { return new ValueSpinnerModel(); } - + //////////// JSlider model //////////// - + private class ValueSliderModel implements BoundedRangeModel, StateChangeListener, Invalidatable { private static final int MAX = 1000; - + /* * Use linear scale value = linear1 * x + linear0 when x < linearPosition * Use quadratic scale value = quad2 * x^2 + quad1 * x + quad0 otherwise */ private final boolean islinear; - + // Linear in range x <= linearPosition private final double linearPosition; - + // May be changing DoubleModels when using linear model private final DoubleModel min, mid, max; - + // Linear multiplier and constant //private final double linear1; //private final double linear0; - + // Non-linear multiplier, exponent and constant private double quad2, quad1, quad0; - + public ValueSliderModel(DoubleModel min, DoubleModel max) { this.islinear = true; linearPosition = 1.0; - + this.min = min; this.mid = max; // Never use exponential scale this.max = max; - + min.addChangeListener(this); max.addChangeListener(this); - + quad2 = quad1 = quad0 = 0; // Not used } - - - + + + /** * Generate a linear model from min to max. */ public ValueSliderModel(double min, double max) { this.islinear = true; linearPosition = 1.0; - + this.min = new DoubleModel(min); this.mid = new DoubleModel(max); // Never use exponential scale this.max = new DoubleModel(max); - + quad2 = quad1 = quad0 = 0; // Not used } - + public ValueSliderModel(double min, double mid, double max) { this(min, 0.5, mid, max); } - + public ValueSliderModel(double min, double mid, DoubleModel max) { this(min, 0.5, mid, max); } - + /* * v(x) = mul * x^exp + add * @@ -240,31 +235,32 @@ public class DoubleModel implements StateChangeListener, ChangeSource, Invalidat * v(1) = mul + add = max * v'(pos) = mul*exp * pos^(exp-1) = linearMul */ - public ValueSliderModel(double min, double pos, double mid, double max ) { + public ValueSliderModel(double min, double pos, double mid, double max) { this(min, pos, mid, new DoubleModel(max)); } + public ValueSliderModel(double min, double pos, double mid, DoubleModel max) { this.min = new DoubleModel(min); this.mid = new DoubleModel(mid); this.max = max; - + this.islinear = false; - + max.addChangeListener(this); - + linearPosition = pos; //linear0 = min; //linear1 = (mid-min)/pos; - + if (!(min < mid && mid <= max.getValue() && 0 < pos && pos < 1)) { throw new IllegalArgumentException("Bad arguments for ValueSliderModel " + "min=" + min + " mid=" + mid + " max=" + max + " pos=" + pos); } - + updateExponentialParameters(); - + } - + private void updateExponentialParameters() { double pos = this.linearPosition; double minValue = this.min.getValue(); @@ -281,11 +277,11 @@ public class DoubleModel implements StateChangeListener, ChangeSource, Invalidat quad1 = (delta + 2 * (midValue - maxValue) * pos - delta * pos * pos) / pow2(pos - 1); quad0 = (midValue - (2 * midValue + delta) * pos + (maxValue + delta) * pos * pos) / pow2(pos - 1); } - + private double pow2(double x) { return x * x; } - + @Override public int getValue() { double value = DoubleModel.this.getValue(); @@ -293,13 +289,13 @@ public class DoubleModel implements StateChangeListener, ChangeSource, Invalidat return 0; if (value >= max.getValue()) return MAX; - + double x; if (value <= mid.getValue()) { // Use linear scale //linear0 = min; //linear1 = (mid-min)/pos; - + x = (value - min.getValue()) * linearPosition / (mid.getValue() - min.getValue()); } else { // Use quadratic scale @@ -309,8 +305,8 @@ public class DoubleModel implements StateChangeListener, ChangeSource, Invalidat } return (int) (x * MAX); } - - + + @Override public void setValue(int newValue) { if (firing > 0) { @@ -319,96 +315,96 @@ public class DoubleModel implements StateChangeListener, ChangeSource, Invalidat " value=" + newValue + ", currently firing events"); return; } - + double x = (double) newValue / MAX; double scaledValue; - + if (x <= linearPosition) { // Use linear scale //linear0 = min; //linear1 = (mid-min)/pos; - + scaledValue = (mid.getValue() - min.getValue()) / linearPosition * x + min.getValue(); } else { // Use quadratic scale scaledValue = quad2 * x * x + quad1 * x + quad0; } - + double converted = currentUnit.fromUnit(currentUnit.round(currentUnit.toUnit(scaledValue))); log.user("SliderModel setValue called for " + DoubleModel.this.toString() + " newValue=" + newValue + " scaledValue=" + scaledValue + " converted=" + converted); DoubleModel.this.setValue(converted); } - - + + // Static get-methods private boolean isAdjusting; - + @Override public int getExtent() { return 0; } - + @Override public int getMaximum() { return MAX; } - + @Override public int getMinimum() { return 0; } - + @Override public boolean getValueIsAdjusting() { return isAdjusting; } - + // Ignore set-values @Override public void setExtent(int newExtent) { } - + @Override public void setMaximum(int newMaximum) { } - + @Override public void setMinimum(int newMinimum) { } - + @Override public void setValueIsAdjusting(boolean b) { isAdjusting = b; } - + @Override public void setRangeProperties(int value, int extent, int min, int max, boolean adjusting) { setValueIsAdjusting(adjusting); setValue(value); } - + // Pass change listeners to the underlying model @Override public void addChangeListener(ChangeListener l) { DoubleModel.this.addChangeListener(l); } - + @Override public void removeChangeListener(ChangeListener l) { DoubleModel.this.removeChangeListener(l); } - + @Override public void invalidate() { DoubleModel.this.invalidate(); } - + @Override public void stateChanged(EventObject e) { // Min or max range has changed. - if ( !islinear ) { - double midValue = (max.getValue() - min.getValue()) /3.0; + if (!islinear) { + double midValue = (max.getValue() - min.getValue()) / 3.0; mid.setValue(midValue); updateExponentialParameters(); } @@ -417,48 +413,48 @@ public class DoubleModel implements StateChangeListener, ChangeSource, Invalidat fireStateChanged(); } } - - + + public BoundedRangeModel getSliderModel(DoubleModel min, DoubleModel max) { return new ValueSliderModel(min, max); } - + public BoundedRangeModel getSliderModel(double min, double max) { return new ValueSliderModel(min, max); } - + public BoundedRangeModel getSliderModel(double min, double mid, double max) { return new ValueSliderModel(min, mid, max); } - + public BoundedRangeModel getSliderModel(double min, double mid, DoubleModel max) { return new ValueSliderModel(min, mid, max); } - + public BoundedRangeModel getSliderModel(double min, double pos, double mid, double max) { return new ValueSliderModel(min, pos, mid, max); } - - - - - + + + + + //////////// Action model //////////// - + private class AutomaticActionModel extends AbstractAction implements StateChangeListener, Invalidatable { private boolean oldValue = false; - + public AutomaticActionModel() { oldValue = isAutomatic(); addChangeListener(this); } - - + + @Override public boolean isEnabled() { return isAutomaticAvailable(); } - + @Override public Object getValue(String key) { if (key.equals(Action.SELECTED_KEY)) { @@ -467,7 +463,7 @@ public class DoubleModel implements StateChangeListener, ChangeSource, Invalidat } return super.getValue(key); } - + @Override public void putValue(String key, Object value) { if (firing > 0) { @@ -486,24 +482,24 @@ public class DoubleModel implements StateChangeListener, ChangeSource, Invalidat super.putValue(key, value); } } - + // Implement a wrapper to the ChangeListeners ArrayList propertyChangeListeners = new ArrayList(); - + @Override public void addPropertyChangeListener(PropertyChangeListener listener) { propertyChangeListeners.add(listener); DoubleModel.this.addChangeListener(this); } - + @Override public void removePropertyChangeListener(PropertyChangeListener listener) { propertyChangeListeners.remove(listener); if (propertyChangeListeners.isEmpty()) DoubleModel.this.removeChangeListener(this); } - + // If the value has changed, generate an event to the listeners @Override public void stateChanged(EventObject e) { @@ -518,18 +514,18 @@ public class DoubleModel implements StateChangeListener, ChangeSource, Invalidat ((PropertyChangeListener) l[i]).propertyChange(event); } } - + @Override public void actionPerformed(ActionEvent e) { // Setting performed in putValue } - + @Override public void invalidate() { DoubleModel.this.invalidate(); } } - + /** * Returns a new Action corresponding to the changes of the automatic setting * property of the value model. This may be used directly with e.g. check buttons. @@ -539,48 +535,48 @@ public class DoubleModel implements StateChangeListener, ChangeSource, Invalidat public Action getAutomaticAction() { return new AutomaticActionModel(); } - - - - - + + + + + //////////// Main model ///////////// - + /* * The main model handles all values in SI units, i.e. no conversion is made within the model. */ - + private final ChangeSource source; private final String valueName; private final double multiplier; - + private final Method getMethod; private final Method setMethod; - + private final Method getAutoMethod; private final Method setAutoMethod; - + private final ArrayList listeners = new ArrayList(); - + private final UnitGroup units; private Unit currentUnit; - + private final double minValue; private double maxValue; - + private String toString = null; - - + + private int firing = 0; // >0 when model itself is sending events - - + + // Used to differentiate changes in valueName and other changes in the component: private double lastValue = 0; private boolean lastAutomatic = false; - + private Invalidator invalidator = new Invalidator(this); - - + + /** * Generate a DoubleModel that contains an internal double value. * @@ -589,7 +585,7 @@ public class DoubleModel implements StateChangeListener, ChangeSource, Invalidat public DoubleModel(double value) { this(value, UnitGroup.UNITS_NONE, Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY); } - + /** * Generate a DoubleModel that contains an internal double value. * @@ -599,7 +595,7 @@ public class DoubleModel implements StateChangeListener, ChangeSource, Invalidat public DoubleModel(double value, UnitGroup unit) { this(value, unit, Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY); } - + /** * Generate a DoubleModel that contains an internal double value. * @@ -610,7 +606,7 @@ public class DoubleModel implements StateChangeListener, ChangeSource, Invalidat public DoubleModel(double value, UnitGroup unit, double min) { this(value, unit, min, Double.POSITIVE_INFINITY); } - + /** * Generate a DoubleModel that contains an internal double value. * @@ -623,18 +619,18 @@ public class DoubleModel implements StateChangeListener, ChangeSource, Invalidat this.lastValue = value; this.minValue = min; this.maxValue = max; - + source = null; valueName = "Constant value"; multiplier = 1; - + getMethod = setMethod = null; getAutoMethod = setAutoMethod = null; units = unit; currentUnit = units.getDefaultUnit(); } - - + + /** * Generates a new DoubleModel that changes the values of the specified component. * The double value is read and written using the methods "get"/"set" + valueName. @@ -650,37 +646,37 @@ public class DoubleModel implements StateChangeListener, ChangeSource, Invalidat this.source = source; this.valueName = valueName; this.multiplier = multiplier; - + this.units = unit; currentUnit = units.getDefaultUnit(); - + this.minValue = min; this.maxValue = max; - + try { getMethod = source.getClass().getMethod("get" + valueName); } catch (NoSuchMethodException e) { throw new IllegalArgumentException("get method for value '" + valueName + "' not present in class " + source.getClass().getCanonicalName()); } - + Method s = null; try { s = source.getClass().getMethod("set" + valueName, double.class); } catch (NoSuchMethodException e1) { } // Ignore setMethod = s; - + // Automatic selection methods - + Method set = null, get = null; - + try { get = source.getClass().getMethod("is" + valueName + "Automatic"); set = source.getClass().getMethod("set" + valueName + "Automatic", boolean.class); } catch (NoSuchMethodException e) { } // ignore - + if (set != null && get != null) { getAutoMethod = get; setAutoMethod = set; @@ -688,53 +684,53 @@ public class DoubleModel implements StateChangeListener, ChangeSource, Invalidat getAutoMethod = null; setAutoMethod = null; } - + } - + public DoubleModel(ChangeSource source, String valueName, double multiplier, UnitGroup unit, double min) { this(source, valueName, multiplier, unit, min, Double.POSITIVE_INFINITY); } - + public DoubleModel(ChangeSource source, String valueName, double multiplier, UnitGroup unit) { this(source, valueName, multiplier, unit, Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY); } - + public DoubleModel(ChangeSource source, String valueName, UnitGroup unit, double min, double max) { this(source, valueName, 1.0, unit, min, max); } - + public DoubleModel(ChangeSource source, String valueName, UnitGroup unit, double min) { this(source, valueName, 1.0, unit, min, Double.POSITIVE_INFINITY); } - + public DoubleModel(ChangeSource source, String valueName, UnitGroup unit) { this(source, valueName, 1.0, unit, Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY); } - + public DoubleModel(ChangeSource source, String valueName) { this(source, valueName, 1.0, UnitGroup.UNITS_NONE, Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY); } - + public DoubleModel(ChangeSource source, String valueName, double min) { this(source, valueName, 1.0, UnitGroup.UNITS_NONE, min, Double.POSITIVE_INFINITY); } - + public DoubleModel(ChangeSource source, String valueName, double min, double max) { this(source, valueName, 1.0, UnitGroup.UNITS_NONE, min, max); } - - - + + + /** * Returns the value of the variable (in SI units). */ public double getValue() { if (getMethod == null) // Constant value return lastValue; - + try { return (Double) getMethod.invoke(source) * multiplier; } catch (IllegalArgumentException e) { @@ -745,14 +741,14 @@ public class DoubleModel implements StateChangeListener, ChangeSource, Invalidat throw Reflection.handleWrappedException(e); } } - + /** * Sets the value of the variable. * @param v New value for parameter in SI units. */ public void setValue(double v) { checkState(true); - + log.debug("Setting value " + v + " for " + this); if (setMethod == null) { if (getMethod != null) { @@ -763,7 +759,7 @@ public class DoubleModel implements StateChangeListener, ChangeSource, Invalidat fireStateChanged(); return; } - + try { setMethod.invoke(source, v / multiplier); } catch (IllegalArgumentException e) { @@ -774,14 +770,14 @@ public class DoubleModel implements StateChangeListener, ChangeSource, Invalidat throw Reflection.handleWrappedException(e); } } - + /** * Returns whether setting the value automatically is available. */ public boolean isAutomaticAvailable() { return (getAutoMethod != null) && (setAutoMethod != null); } - + /** * Returns whether the value is currently being set automatically. * Returns false if automatic setting is not available at all. @@ -789,7 +785,7 @@ public class DoubleModel implements StateChangeListener, ChangeSource, Invalidat public boolean isAutomatic() { if (getAutoMethod == null) return false; - + try { return (Boolean) getAutoMethod.invoke(source); } catch (IllegalArgumentException e) { @@ -800,20 +796,20 @@ public class DoubleModel implements StateChangeListener, ChangeSource, Invalidat throw Reflection.handleWrappedException(e); } } - + /** * Sets whether the value should be set automatically. Simply fires a * state change event if automatic setting is not available. */ public void setAutomatic(boolean auto) { checkState(true); - + if (setAutoMethod == null) { log.debug("Setting automatic to " + auto + " for " + this + ", automatic not available"); fireStateChanged(); // in case something is out-of-sync return; } - + log.debug("Setting automatic to " + auto + " for " + this); lastAutomatic = auto; try { @@ -826,8 +822,8 @@ public class DoubleModel implements StateChangeListener, ChangeSource, Invalidat throw Reflection.handleWrappedException(e); } } - - + + /** * Returns the current Unit. At the beginning it is the default unit of the UnitGroup. * @return The most recently set unit. @@ -835,7 +831,7 @@ public class DoubleModel implements StateChangeListener, ChangeSource, Invalidat public Unit getCurrentUnit() { return currentUnit; } - + /** * Sets the current Unit. The unit must be one of those included in the UnitGroup. * @param u The unit to set active. @@ -848,8 +844,8 @@ public class DoubleModel implements StateChangeListener, ChangeSource, Invalidat currentUnit = u; fireStateChanged(); } - - + + /** * Returns the UnitGroup associated with the parameter value. * @@ -858,9 +854,9 @@ public class DoubleModel implements StateChangeListener, ChangeSource, Invalidat public UnitGroup getUnitGroup() { return units; } - - - + + + /** * Add a listener to the model. Adds the model as a listener to the value source if this * is the first listener. @@ -869,7 +865,7 @@ public class DoubleModel implements StateChangeListener, ChangeSource, Invalidat @Override public void addChangeListener(EventListener l) { checkState(true); - + if (listeners.isEmpty()) { if (source != null) { source.addChangeListener(this); @@ -877,11 +873,11 @@ public class DoubleModel implements StateChangeListener, ChangeSource, Invalidat lastAutomatic = isAutomatic(); } } - + listeners.add(l); log.verbose(this + " adding listener (total " + listeners.size() + "): " + l); } - + /** * Remove a listener from the model. Removes the model from being a listener to the Component * if this was the last listener of the model. @@ -890,15 +886,15 @@ public class DoubleModel implements StateChangeListener, ChangeSource, Invalidat @Override public void removeChangeListener(EventListener l) { checkState(false); - + listeners.remove(l); if (listeners.isEmpty() && source != null) { source.removeChangeListener(this); } log.verbose(this + " removing listener (total " + listeners.size() + "): " + l); } - - + + /** * Invalidates this model by removing all listeners and removing this from * listening to the source. After invalidation no listeners can be added to this @@ -908,7 +904,7 @@ public class DoubleModel implements StateChangeListener, ChangeSource, Invalidat public void invalidate() { log.verbose("Invalidating " + this); invalidator.invalidate(); - + if (!listeners.isEmpty()) { log.warn("Invalidating " + this + " while still having listeners " + listeners); } @@ -918,13 +914,13 @@ public class DoubleModel implements StateChangeListener, ChangeSource, Invalidat } MemoryManagement.collectable(this); } - - + + private void checkState(boolean error) { invalidator.check(error); } - - + + @Override protected void finalize() throws Throwable { super.finalize(); @@ -932,29 +928,29 @@ public class DoubleModel implements StateChangeListener, ChangeSource, Invalidat log.warn(this + " being garbage-collected while having listeners " + listeners); } }; - - + + /** * Fire a ChangeEvent to all listeners. */ protected void fireStateChanged() { checkState(true); - + EventObject event = new EventObject(this); ChangeEvent cevent = new ChangeEvent(this); firing++; // Copy the list before iterating to prevent concurrent modification exceptions. EventListener[] ls = listeners.toArray(new EventListener[0]); for (EventListener l : ls) { - if ( l instanceof StateChangeListener ) { - ((StateChangeListener)l).stateChanged(event); - } else if ( l instanceof ChangeListener ) { - ((ChangeListener)l).stateChanged(cevent); + if (l instanceof StateChangeListener) { + ((StateChangeListener) l).stateChanged(event); + } else if (l instanceof ChangeListener) { + ((ChangeListener) l).stateChanged(cevent); } } firing--; } - + /** * Called when the component changes. Checks whether the modeled value has changed, and if * it has, updates lastValue and generates ChangeEvents for all listeners of the model. @@ -962,7 +958,7 @@ public class DoubleModel implements StateChangeListener, ChangeSource, Invalidat @Override public void stateChanged(EventObject e) { checkState(true); - + double v = getValue(); boolean b = isAutomatic(); if (lastValue == v && lastAutomatic == b) @@ -971,8 +967,8 @@ public class DoubleModel implements StateChangeListener, ChangeSource, Invalidat lastAutomatic = b; fireStateChanged(); } - - + + /** * Explain the DoubleModel as a String. */ diff --git a/core/src/net/sf/openrocket/util/ExpressionParser.java b/core/src/net/sf/openrocket/util/ExpressionParser.java new file mode 100644 index 00000000..091a3796 --- /dev/null +++ b/core/src/net/sf/openrocket/util/ExpressionParser.java @@ -0,0 +1,37 @@ +package net.sf.openrocket.util; + +import de.congrace.exp4j.Calculable; +import de.congrace.exp4j.ExpressionBuilder; +import de.congrace.exp4j.UnknownFunctionException; +import de.congrace.exp4j.UnparsableExpressionException; + +public class ExpressionParser { + + + public double parse(String expression) throws InvalidExpressionException { + try { + ExpressionBuilder builder = new ExpressionBuilder(modify(expression)); + Calculable calc = builder.build(); + return calc.calculate(); + } catch (java.lang.NumberFormatException e) { + throw new InvalidExpressionException("Invalid expression: " + expression, e); + } catch (UnknownFunctionException e) { + throw new InvalidExpressionException("Invalid expression: " + expression, e); + } catch (UnparsableExpressionException e) { + throw new InvalidExpressionException("Invalid expression: " + expression, e); + } catch (java.util.EmptyStackException e) { + throw new InvalidExpressionException("Invalid expression: " + expression, e); + } + } + + private String modify(String exp) throws InvalidExpressionException { + exp = exp.replaceAll("(\\d+)\\s+(\\d+)\\s*/\\s*(\\d+)", "($1+$2/$3)"); + exp = exp.replace(',', '.'); + // Disallow spaces between numbers - default is to remove spaces! + if (exp.matches(".*[0-9.]\\s+[0-9.].*")) { + throw new InvalidExpressionException("Invalid expression: " + exp); + } + return exp; + } + +} diff --git a/core/src/net/sf/openrocket/util/InvalidExpressionException.java b/core/src/net/sf/openrocket/util/InvalidExpressionException.java new file mode 100644 index 00000000..118cefe0 --- /dev/null +++ b/core/src/net/sf/openrocket/util/InvalidExpressionException.java @@ -0,0 +1,22 @@ +package net.sf.openrocket.util; + +/** + * Exception indicating an invalid expression. + * + * @author Sampo Niskanen + */ +public class InvalidExpressionException extends Exception { + + public InvalidExpressionException(String message) { + super(message); + } + + public InvalidExpressionException(Throwable cause) { + super(cause); + } + + public InvalidExpressionException(String message, Throwable cause) { + super(message, cause); + } + +} diff --git a/core/src/net/sf/openrocket/util/exp4j/AbstractExpression.java b/core/src/net/sf/openrocket/util/exp4j/AbstractExpression.java deleted file mode 100644 index 908e5075..00000000 --- a/core/src/net/sf/openrocket/util/exp4j/AbstractExpression.java +++ /dev/null @@ -1,85 +0,0 @@ -/* - Copyright 2011 frank asseg - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - - */ -package net.sf.openrocket.util.exp4j; - -import java.text.NumberFormat; -import java.util.List; - -/** - * Abstract base class for mathematical expressions - * - * @author fas@congrace.de - */ -abstract class AbstractExpression { - private final String expression; - private final Token[] tokens; - private final String[] variableNames; - private final NumberFormat numberFormat = NumberFormat.getInstance(); - - /** - * Construct a new {@link AbstractExpression} - * - * @param expression - * the mathematical expression to be used - * @param tokens - * the {@link Token}s in the expression - * @param variableNames - * an array of variable names which are used in the expression - */ - AbstractExpression(String expression, Token[] tokens, String[] variableNames) { - this.expression = expression; - this.tokens = tokens; - this.variableNames = variableNames; - } - - /** - * get the mathematical expression {@link String} - * - * @return the expression - */ - public String getExpression() { - return expression; - } - - /** - * get the used {@link NumberFormat} - * - * @return the used {@link NumberFormat} - */ - public NumberFormat getNumberFormat() { - return numberFormat; - } - - /** - * get the {@link Token}s - * - * @return the array of {@link Token}s - */ - Token[] getTokens() { - return tokens; - } - - /** - * get the variable names - * - * @return the {@link List} of variable names - */ - String[] getVariableNames() { - return variableNames; - } - -} diff --git a/core/src/net/sf/openrocket/util/exp4j/Calculable.java b/core/src/net/sf/openrocket/util/exp4j/Calculable.java deleted file mode 100644 index 3fecba51..00000000 --- a/core/src/net/sf/openrocket/util/exp4j/Calculable.java +++ /dev/null @@ -1,33 +0,0 @@ -package net.sf.openrocket.util.exp4j; - -/** - * This is the basic result class of the exp4j {@link ExpressionBuilder} - * - * @author ruckus - * - */ -public interface Calculable { - /** - * calculate the result of the expression - * - * @return the result of the calculation - */ - public double calculate(); - - /** - * return the expression in reverse polish postfix notation - * - * @return the expression used to construct this {@link Calculable} - */ - public String getExpression(); - - /** - * set a variable value for the calculation - * - * @param name - * the variable name - * @param value - * the value of the variable - */ - public void setVariable(String name, double value); -} diff --git a/core/src/net/sf/openrocket/util/exp4j/CalculationToken.java b/core/src/net/sf/openrocket/util/exp4j/CalculationToken.java deleted file mode 100644 index 75d9dc97..00000000 --- a/core/src/net/sf/openrocket/util/exp4j/CalculationToken.java +++ /dev/null @@ -1,30 +0,0 @@ -/* - Copyright 2011 frank asseg - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - - */ -package net.sf.openrocket.util.exp4j; - -import java.util.Map; -import java.util.Stack; - -abstract class CalculationToken extends Token { - - CalculationToken(String value) { - super(value); - } - - abstract void mutateStackForCalculation(Stack stack, Map variableValues); - -} diff --git a/core/src/net/sf/openrocket/util/exp4j/CommandlineInterpreter.java b/core/src/net/sf/openrocket/util/exp4j/CommandlineInterpreter.java deleted file mode 100644 index 443545f0..00000000 --- a/core/src/net/sf/openrocket/util/exp4j/CommandlineInterpreter.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - Copyright 2011 frank asseg - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - - */ -package net.sf.openrocket.util.exp4j; -/** - * Simple commandline interpreter for mathematical expressions the interpreter - * takes a mathematical expressions as a {@link String} argument, evaluates it - * and prints out the result. - * - * - *
- * java de.congrace.exp4j.CommandlineInterpreter "2 * log(2.2223) - ((2-3.221) * 14.232^2)"
- * > 248.91042049521056
- * 
- * - * @author fas@congrace.de - * - */ -public class CommandlineInterpreter { - private static void calculateExpression(String string) { - try { - final PostfixExpression pe = PostfixExpression.fromInfix(string); - System.out.println(pe.calculate()); - } catch (UnparsableExpressionException e) { - e.printStackTrace(); - } catch (UnknownFunctionException e) { - e.printStackTrace(); - } - } - - public static void main(String[] args) { - if (args.length != 1) { - printUsage(); - } else { - calculateExpression(args[0]); - } - } - - private static void printUsage() { - final StringBuilder usage = new StringBuilder(); - usage.append("Commandline Expression Parser\n\n").append("Example: ").append("\n").append("java -jar exp4j.jar \"2.12 * log(23) * (12 - 4)\"\n\n") - .append("written by fas@congrace.de"); - System.err.println(usage.toString()); - } -} diff --git a/core/src/net/sf/openrocket/util/exp4j/CustomFunction.java b/core/src/net/sf/openrocket/util/exp4j/CustomFunction.java deleted file mode 100644 index 8a437af2..00000000 --- a/core/src/net/sf/openrocket/util/exp4j/CustomFunction.java +++ /dev/null @@ -1,84 +0,0 @@ -package net.sf.openrocket.util.exp4j; -import java.util.Map; -import java.util.Stack; - -import net.sf.openrocket.util.exp4j.FunctionToken.Function; - -/** - * this classed is used to create custom functions for exp4j
- *
- * Example
- *
{@code 
- * CustomFunction fooFunc = new CustomFunction("foo") {
- * 		public double applyFunction(double value) {
- * 			return value*Math.E;
- * 		}
- * };
- * double varX=12d;
- * Calculable calc = new ExpressionBuilder("foo(x)").withCustomFunction(fooFunc).withVariable("x",varX).build();
- * assertTrue(calc.calculate() == Math.E * varX);
- * }
- * - * @author ruckus - * - */ -public abstract class CustomFunction extends CalculationToken { - private int argc=1; - - /** - * create a new single value input CustomFunction with a set name - * - * @param value - * the name of the function (e.g. foo) - */ - protected CustomFunction(String value) throws InvalidCustomFunctionException{ - super(value); - for (Function f:Function.values()) { - if (value.equalsIgnoreCase(f.toString())){ - throw new InvalidCustomFunctionException(value + " is already reserved as a function name"); - } - } - } - - /** - * create a new single value input CustomFunction with a set name - * - * @param value - * the name of the function (e.g. foo) - */ - protected CustomFunction(String value,int argumentCount) throws InvalidCustomFunctionException{ - super(value); - this.argc=argumentCount; - for (Function f:Function.values()) { - if (value.equalsIgnoreCase(f.toString())){ - throw new InvalidCustomFunctionException(value + " is already reserved as a function name"); - } - } - } - - /** - * apply the function to a value - * - * @param values - * the values to which the function should be applied. - * @return the function value - */ - public abstract double applyFunction(double[] values); - - @Override - void mutateStackForCalculation(Stack stack, Map variableValues) { - double[] args=new double[argc]; - for (int i=0;i operatorStack, StringBuilder output) { - operatorStack.push(this); - } - public int getArgumentCount() { - return argc; - } -} diff --git a/core/src/net/sf/openrocket/util/exp4j/ExpressionBuilder.java b/core/src/net/sf/openrocket/util/exp4j/ExpressionBuilder.java deleted file mode 100644 index 8e4a4307..00000000 --- a/core/src/net/sf/openrocket/util/exp4j/ExpressionBuilder.java +++ /dev/null @@ -1,131 +0,0 @@ -package net.sf.openrocket.util.exp4j; - -import java.util.Collection; -import java.util.HashSet; -import java.util.LinkedHashMap; -import java.util.Map; -import java.util.Map.Entry; -import java.util.Set; - -/** - * This is Builder implementation for the exp4j API used to create a Calculable - * instance for the user - * - * @author ruckus - * - */ -public class ExpressionBuilder { - private final Map variables = new LinkedHashMap(); - private final Set customFunctions = new HashSet(); - - private String expression; - - /** - * Create a new ExpressionBuilder - * - * @param expression - * the expression to evaluate - */ - public ExpressionBuilder(String expression) { - this.expression = expression; - } - - /** - * build a new {@link Calculable} from the expression using the supplied - * variables - * - * @return the {@link Calculable} which can be used to evaluate the - * expression - * @throws UnknownFunctionException - * when an unrecognized function name is used in the expression - * @throws UnparsableExpressionException - * if the expression could not be parsed - */ - public Calculable build() throws UnknownFunctionException, UnparsableExpressionException { - if (expression.indexOf('=') == -1 && !variables.isEmpty()) { - - // User supplied an expression without leading "f(...)=" - // so we just append the user function to a proper "f()=" - // for PostfixExpression.fromInfix() - StringBuilder function = new StringBuilder("f("); - for (String var : variables.keySet()) { - function.append(var).append(','); - } - expression = function.deleteCharAt(function.length() - 1).toString() + ")=" + expression; - } - // create the PostfixExpression and return it as a Calculable - PostfixExpression delegate = PostfixExpression.fromInfix(expression, customFunctions); - for (String var : variables.keySet()) { - if (variables.get(var) != null) { - delegate.setVariable(var, variables.get(var)); - } - for (CustomFunction custom:customFunctions){ - if (custom.getValue().equals(var)){ - throw new UnparsableExpressionException("variable '" + var + "' cannot have the same name as a custom function " + custom.getValue()); - } - } - } - return delegate; - } - - /** - * add a custom function instance for the evaluator to recognize - * - * @param function - * the {@link CustomFunction} to add - * @return the {@link ExpressionBuilder} instance - */ - public ExpressionBuilder withCustomFunction(CustomFunction function) { - customFunctions.add(function); - return this; - } - - public ExpressionBuilder withCustomFunctions(Collection functions) { - customFunctions.addAll(functions); - return this; - } - - /** - * set the value for a variable - * - * @param variableName - * the variable name e.g. "x" - * @param value - * the value e.g. 2.32d - * @return the {@link ExpressionBuilder} instance - */ - public ExpressionBuilder withVariable(String variableName, double value) { - variables.put(variableName, value); - return this; - } - - /** - * set the variables names used in the expression without setting their - * values - * - * @param variableNames - * vararg {@link String} of the variable names used in the - * expression - * @return the ExpressionBuilder instance - */ - public ExpressionBuilder withVariableNames(String... variableNames) { - for (String variable : variableNames) { - variables.put(variable, null); - } - return this; - } - - /** - * set the values for variables - * - * @param variableMap - * a map of variable names to variable values - * @return the {@link ExpressionBuilder} instance - */ - public ExpressionBuilder withVariables(Map variableMap) { - for (Entry v : variableMap.entrySet()) { - variables.put(v.getKey(), v.getValue()); - } - return this; - } -} diff --git a/core/src/net/sf/openrocket/util/exp4j/FunctionSeparatorToken.java b/core/src/net/sf/openrocket/util/exp4j/FunctionSeparatorToken.java deleted file mode 100644 index 0e69f8ad..00000000 --- a/core/src/net/sf/openrocket/util/exp4j/FunctionSeparatorToken.java +++ /dev/null @@ -1,17 +0,0 @@ -package net.sf.openrocket.util.exp4j; - -import java.util.Stack; - -public class FunctionSeparatorToken extends Token{ - public FunctionSeparatorToken() { - super(","); - } - @Override - void mutateStackForInfixTranslation(Stack operatorStack, StringBuilder output) { - Token token; - while (!((token=operatorStack.peek()) instanceof ParenthesisToken) && !token.getValue().equals("(")){ - output.append(operatorStack.pop().getValue()).append(" "); - } - } - -} diff --git a/core/src/net/sf/openrocket/util/exp4j/FunctionToken.java b/core/src/net/sf/openrocket/util/exp4j/FunctionToken.java deleted file mode 100644 index 10c4ba47..00000000 --- a/core/src/net/sf/openrocket/util/exp4j/FunctionToken.java +++ /dev/null @@ -1,127 +0,0 @@ -/* -Copyright 2011 frank asseg - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. - - */ -package net.sf.openrocket.util.exp4j; - -import java.util.Map; -import java.util.Stack; - -/** - * A {@link Token} for functions - * - * @author fas@congrace.de - * - */ -class FunctionToken extends CalculationToken { - /** - * the functionNames that can be used in an expression - * - * @author ruckus - * - */ - enum Function { - ABS, ACOS, ASIN, ATAN, CBRT, CEIL, COS, COSH, EXP, EXPM1, FLOOR, LOG, SIN, SINH, SQRT, TAN, TANH - } - - private Function function; - - /** - * construct a new {@link FunctionToken} - * - * @param value - * the name of the function - * @throws UnknownFunctionException - * if an unknown function name is encountered - */ - FunctionToken(String value) throws UnknownFunctionException { - super(value); - try { - function = Function.valueOf(value.toUpperCase()); - } catch (IllegalArgumentException e) { - throw new UnknownFunctionException(value); - } - if (function == null) { - throw new UnknownFunctionException(value); - } - } - - /** - * apply a function to a value x - * - * @param x - * the value the function should be applied to - * @return the result of the function - */ - double applyFunction(double x) { - switch (function) { - case ABS: - return Math.abs(x); - case ACOS: - return Math.acos(x); - case ASIN: - return Math.asin(x); - case ATAN: - return Math.atan(x); - case CBRT: - return Math.cbrt(x); - case CEIL: - return Math.ceil(x); - case COS: - return Math.cos(x); - case COSH: - return Math.cosh(x); - case EXP: - return Math.exp(x); - case EXPM1: - return Math.expm1(x); - case FLOOR: - return Math.floor(x); - case LOG: - return Math.log(x); - case SIN: - return Math.sin(x); - case SINH: - return Math.sinh(x); - case SQRT: - return Math.sqrt(x); - case TAN: - return Math.tan(x); - case TANH: - return Math.tanh(x); - default: - return Double.NaN; // should not happen ;) - } - } - - /** - * get the {@link Function} - * - * @return the correspoding {@link Function} - */ - Function getFunction() { - return function; - } - - @Override - void mutateStackForCalculation(Stack stack, Map variableValues) { - stack.push(this.applyFunction(stack.pop())); - } - - @Override - void mutateStackForInfixTranslation(Stack operatorStack, StringBuilder output) { - operatorStack.push(this); - } -} diff --git a/core/src/net/sf/openrocket/util/exp4j/InfixTranslator.java b/core/src/net/sf/openrocket/util/exp4j/InfixTranslator.java deleted file mode 100644 index a1070092..00000000 --- a/core/src/net/sf/openrocket/util/exp4j/InfixTranslator.java +++ /dev/null @@ -1,109 +0,0 @@ -/* - Copyright 2011 frank asseg - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - - */ -package net.sf.openrocket.util.exp4j; - -import java.util.Set; -import java.util.Stack; - -/** - * Translate a mathematical expression in human readable infix notation to a - * Reverse Polish Notation (postfix) expression for easier parsing. by - * implementing the shunting yard algorithm by dijkstra - * - * @author fas@congrace.de - */ -class InfixTranslator { - - private static String substituteUnaryOperators(String expr) { - final StringBuilder exprBuilder = new StringBuilder(expr.length()); - final char[] data = expr.toCharArray(); - char lastChar = ' '; - for (int i = 0; i < expr.length(); i++) { - if (exprBuilder.length() > 0) { - lastChar = exprBuilder.charAt(exprBuilder.length() - 1); - } - final char c = data[i]; - switch (c) { - case '+': - if (i > 0 && lastChar != '(' && !(OperatorToken.isOperator(lastChar))) { - exprBuilder.append(c); - } - break; - case '-': - if (i > 0 && lastChar != '(' && !(OperatorToken.isOperator(lastChar))) { - exprBuilder.append(c); - } else { - exprBuilder.append('#'); - } - break; - default: - if (!Character.isWhitespace(c)) { - exprBuilder.append(c); - } - } - } - return exprBuilder.toString(); - } - - /** - * Delegation method for simple expression without variables or custom - * functions - * - * @param infixExpression - * the infix expression to be translated - * @return translated RNP postfix expression - * @throws UnparsableExpressionException - * when the expression is invalid - * @throws UnknownFunctionException - * when an unknown function has been used in the input. - */ - static String toPostfixExpression(String infixExpression) throws UnparsableExpressionException, UnknownFunctionException { - return toPostfixExpression(infixExpression, null, null); - } - - /** - * implement the shunting yard algorithm - * - * @param infixExpression - * the human readable expression which should be translated to - * RPN - * @param variableNames - * the variable names used in the expression - * @param customFunctions - * the CustomFunction implementations used - * @return the expression in postfix format - * @throws UnparsableExpressionException - * if the expression could not be translated to RPN - * @throws UnknownFunctionException - * if an unknown function was encountered - */ - static String toPostfixExpression(String infixExpression, String[] variableNames, Set customFunctions) - throws UnparsableExpressionException, UnknownFunctionException { - infixExpression = substituteUnaryOperators(infixExpression); - final Token[] tokens = new Tokenizer(variableNames, customFunctions).tokenize(infixExpression); - final StringBuilder output = new StringBuilder(tokens.length); - final Stack operatorStack = new Stack(); - for (final Token token : tokens) { - token.mutateStackForInfixTranslation(operatorStack, output); - } - // all tokens read, put the rest of the operations on the output; - while (operatorStack.size() > 0) { - output.append(operatorStack.pop().getValue()).append(" "); - } - return output.toString().trim(); - } -} diff --git a/core/src/net/sf/openrocket/util/exp4j/InvalidCustomFunctionException.java b/core/src/net/sf/openrocket/util/exp4j/InvalidCustomFunctionException.java deleted file mode 100644 index de2da3cc..00000000 --- a/core/src/net/sf/openrocket/util/exp4j/InvalidCustomFunctionException.java +++ /dev/null @@ -1,9 +0,0 @@ -package net.sf.openrocket.util.exp4j; - -public class InvalidCustomFunctionException extends Exception{ - private static final long serialVersionUID = 1L; - - public InvalidCustomFunctionException(String message) { - super(message); - } -} diff --git a/core/src/net/sf/openrocket/util/exp4j/NumberToken.java b/core/src/net/sf/openrocket/util/exp4j/NumberToken.java deleted file mode 100644 index 97017bda..00000000 --- a/core/src/net/sf/openrocket/util/exp4j/NumberToken.java +++ /dev/null @@ -1,66 +0,0 @@ -/* - Copyright 2011 frank asseg - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - - */ -package net.sf.openrocket.util.exp4j; - -import java.util.Map; -import java.util.Stack; - -/** - * A {@link Token} for Numbers - * - * @author fas@congrace.de - * - */ -class NumberToken extends CalculationToken { - - private final double doubleValue; - - /** - * construct a new {@link NumberToken} - * - * @param value - * the value of the number as a {@link String} - */ - NumberToken(String value) { - super(value); - this.doubleValue = Double.parseDouble(value); - } - - @Override - public boolean equals(Object obj) { - if (obj instanceof NumberToken) { - final NumberToken t = (NumberToken) obj; - return t.getValue().equals(this.getValue()); - } - return false; - } - - @Override - public int hashCode() { - return getValue().hashCode(); - } - - @Override - void mutateStackForCalculation(Stack stack, Map variableValues) { - stack.push(this.doubleValue); - } - - @Override - void mutateStackForInfixTranslation(Stack operatorStack, StringBuilder output) { - output.append(this.getValue()).append(' '); - } -} diff --git a/core/src/net/sf/openrocket/util/exp4j/OperatorToken.java b/core/src/net/sf/openrocket/util/exp4j/OperatorToken.java deleted file mode 100644 index cff78846..00000000 --- a/core/src/net/sf/openrocket/util/exp4j/OperatorToken.java +++ /dev/null @@ -1,208 +0,0 @@ -/* - Copyright 2011 frank asseg - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - - */ -package net.sf.openrocket.util.exp4j; - -import java.util.Map; -import java.util.Stack; - -/** - * {@link Token} for Operations like +,-,*,/,% and ^ - * - * @author fas@congrace.de - */ -class OperatorToken extends CalculationToken { - - /** - * the valid {@link Operation}s for the {@link OperatorToken} - * - * @author fas@congrace.de - */ - enum Operation { - ADDITION(1, true), SUBTRACTION(1, true), MULTIPLICATION(2, true), DIVISION(2, true), MODULO(2, true), EXPONENTIATION(3, false), UNARY_MINUS(4, false), UNARY_PLUS( - 4, false); - private final int precedence; - private final boolean leftAssociative; - - private Operation(int precedence, boolean leftAssociative) { - this.precedence = precedence; - this.leftAssociative = leftAssociative; - } - } - - /** - * return a corresponding {@link Operation} for a symbol - * - * @param c - * the symbol of the operation - * @return the corresponding {@link Operation} - */ - static Operation getOperation(char c) { - switch (c) { - case '+': - return Operation.ADDITION; - case '-': - return Operation.SUBTRACTION; - case '*': - return Operation.MULTIPLICATION; - case '/': - return Operation.DIVISION; - case '^': - return Operation.EXPONENTIATION; - case '#': - return Operation.UNARY_MINUS; - case '%': - return Operation.MODULO; - default: - return null; - } - } - - static boolean isOperator(char c) { - return getOperation(c) != null; - } - - private final Operation operation; - - /** - * construct a new {@link OperatorToken} - * - * @param value - * the symbol (e.g.: '+') - * @param operation - * the {@link Operation} of this {@link Token} - */ - OperatorToken(String value, Operation operation) { - super(value); - this.operation = operation; - } - - /** - * apply the {@link Operation} - * - * @param values - * the doubles to operate on - * @return the result of the {@link Operation} - */ - double applyOperation(double... values) { - switch (operation) { - case ADDITION: - return values[0] + values[1]; - case SUBTRACTION: - return values[0] - values[1]; - case MULTIPLICATION: - return values[0] * values[1]; - case EXPONENTIATION: - return Math.pow(values[0], values[1]); - case DIVISION: - return values[0] / values[1]; - case UNARY_MINUS: - return -values[0]; - case UNARY_PLUS: - return values[0]; - case MODULO: - return values[0] % values[1]; - default: - return 0; - } - } - - @Override - public boolean equals(Object obj) { - if (obj instanceof OperatorToken) { - final OperatorToken t = (OperatorToken) obj; - return t.getValue().equals(this.getValue()); - } - return false; - } - - int getOperandCount() { - switch (operation) { - case ADDITION: - case SUBTRACTION: - case MULTIPLICATION: - case DIVISION: - case EXPONENTIATION: - case MODULO: - return 2; - case UNARY_MINUS: - case UNARY_PLUS: - return 1; - default: - return 0; - } - } - - /** - * get the {@link Operation} of this {@link Token} - * - * @return the {@link Operation} - */ - Operation getOperation() { - return operation; - } - - int getPrecedence() { - return operation.precedence; - } - - @Override - public int hashCode() { - return getValue().hashCode(); - } - - /** - * check if the operation is left associative - * - * @return true if left associative, otherwise false - */ - boolean isLeftAssociative() { - return operation.leftAssociative; - } - - @Override - void mutateStackForCalculation(Stack stack, Map variableValues) { - if (this.getOperandCount() == 2) { - final double n2 = stack.pop(); - final double n1 = stack.pop(); - stack.push(this.applyOperation(n1, n2)); - } else if (this.getOperandCount() == 1) { - final double n1 = stack.pop(); - stack.push(this.applyOperation(n1)); - } - } - - @Override - void mutateStackForInfixTranslation(Stack operatorStack, StringBuilder output) { - Token before; - while (!operatorStack.isEmpty() && (before = operatorStack.peek()) != null && (before instanceof OperatorToken || before instanceof FunctionToken)) { - if (before instanceof FunctionToken) { - operatorStack.pop(); - output.append(before.getValue()).append(" "); - } else { - final OperatorToken stackOperator = (OperatorToken) before; - if (this.isLeftAssociative() && this.getPrecedence() <= stackOperator.getPrecedence()) { - output.append(operatorStack.pop().getValue()).append(" "); - } else if (!this.isLeftAssociative() && this.getPrecedence() < stackOperator.getPrecedence()) { - output.append(operatorStack.pop().getValue()).append(" "); - } else { - break; - } - } - } - operatorStack.push(this); - } -} diff --git a/core/src/net/sf/openrocket/util/exp4j/ParenthesisToken.java b/core/src/net/sf/openrocket/util/exp4j/ParenthesisToken.java deleted file mode 100644 index 2a365811..00000000 --- a/core/src/net/sf/openrocket/util/exp4j/ParenthesisToken.java +++ /dev/null @@ -1,69 +0,0 @@ -/* - Copyright 2011 frank asseg - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - - */ -package net.sf.openrocket.util.exp4j; - -import java.util.Stack; - -/** - * Token for parenthesis - * - * @author fas@congrace.de - */ -class ParenthesisToken extends Token { - - ParenthesisToken(String value) { - super(value); - } - - @Override - public boolean equals(Object obj) { - if (obj instanceof ParenthesisToken) { - final ParenthesisToken t = (ParenthesisToken) obj; - return t.getValue().equals(this.getValue()); - } - return false; - } - - @Override - public int hashCode() { - return getValue().hashCode(); - } - - /** - * check the direction of the parenthesis - * - * @return true if it's a left parenthesis (open) false if it is a right - * parenthesis (closed) - */ - boolean isOpen() { - return getValue().equals("(") || getValue().equals("[") || getValue().equals("{"); - } - - @Override - void mutateStackForInfixTranslation(Stack operatorStack, StringBuilder output) { - if (this.isOpen()) { - operatorStack.push(this); - } else { - Token next; - while ((next = operatorStack.peek()) instanceof OperatorToken || next instanceof FunctionToken || next instanceof CustomFunction - || (next instanceof ParenthesisToken && !((ParenthesisToken) next).isOpen())) { - output.append(operatorStack.pop().getValue()).append(" "); - } - operatorStack.pop(); - } - } -} diff --git a/core/src/net/sf/openrocket/util/exp4j/PostfixExpression.java b/core/src/net/sf/openrocket/util/exp4j/PostfixExpression.java deleted file mode 100644 index 36ef5799..00000000 --- a/core/src/net/sf/openrocket/util/exp4j/PostfixExpression.java +++ /dev/null @@ -1,144 +0,0 @@ -/* - Copyright 2011 frank asseg - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - - */ -package net.sf.openrocket.util.exp4j; - -import java.util.HashMap; -import java.util.Map; -import java.util.Set; -import java.util.Stack; - -/** - * Class for calculating values from a RPN postfix expression.
- * The default way to create a new instance of {@link PostfixExpression} is by - * using the static factory method fromInfix() - * - * @author fas@congrace.de - */ -public final class PostfixExpression extends AbstractExpression implements Calculable { - /** - * Factory method for creating {@link PostfixExpression}s from human - * readable infix expressions - * - * @param expression - * the infix expression to be used - * @return an equivalent {@link PostfixExpression} - * @throws UnparsableExpressionException - * if the expression was invalid - * @throws UnknownFunctionException - * if an unknown function has been used - * @deprecated please use {@link ExpressionBuilder} API - */ - @Deprecated - public static PostfixExpression fromInfix(String expression) throws UnparsableExpressionException, UnknownFunctionException { - return fromInfix(expression, null); - } - - /** - * Factory method for creating {@link PostfixExpression}s from human - * readable infix expressions - * - * @param expression - * the infix expression to be used - * @param customFunctions - * the CustomFunction implementations used - * @return an equivalent {@link PostfixExpression} - * @throws UnparsableExpressionException - * if the expression was invalid - * @throws UnknownFunctionException - * if an unknown function has been used - * @deprecated please use {@link ExpressionBuilder} - */ - @Deprecated - public static PostfixExpression fromInfix(String expression, Set customFunctions) throws UnparsableExpressionException, - UnknownFunctionException { - String[] variables = null; - int posStart, posEnd; - if ((posStart = expression.indexOf('=')) > 0) { - String functionDef = expression.substring(0, posStart); - expression = expression.substring(posStart + 1); - if ((posStart = functionDef.indexOf('(')) > 0 && (posEnd = functionDef.indexOf(')')) > 0) { - variables = functionDef.substring(posStart + 1, posEnd).split(","); - } - } - return new PostfixExpression(InfixTranslator.toPostfixExpression(expression, variables, customFunctions), variables, customFunctions); - } - - private final Map variableValues = new HashMap(); - - /** - * Construct a new simple {@link PostfixExpression} - * - * @param expression - * the postfix expression to be calculated - * @param variableNames - * the variable names in the expression - * @param customFunctions - * the CustomFunction implementations used - * @throws UnparsableExpressionException - * when expression is invalid - * @throws UnknownFunctionException - * when an unknown function has been used - */ - private PostfixExpression(String expression, String[] variableNames, Set customFunctions) throws UnparsableExpressionException, - UnknownFunctionException { - super(expression, new Tokenizer(variableNames, customFunctions).tokenize(expression), variableNames); - } - - /** - * delegate the calculation of a simple expression without variables - * - * @return the result - */ - public double calculate() { - return calculate(null); - } - - /** - * calculate the result of the expression and substitute the variables by - * their values beforehand - * - * @param values - * the variable values to be substituted - * @return the result of the calculation - * @throws IllegalArgumentException - * if the variables are invalid - */ - public double calculate(double... values) throws IllegalArgumentException { - if (getVariableNames() == null && values != null) { - throw new IllegalArgumentException("there are no variables to set values"); - } else if (getVariableNames() != null && values == null && variableValues.isEmpty()) { - throw new IllegalAccessError("variable values have to be set"); - } else if (values != null && values.length != getVariableNames().length) { - throw new IllegalArgumentException("The are an unequal number of variables and arguments"); - } - int i = 0; - if (getVariableNames() != null && values != null) { - for (double val : values) { - variableValues.put(getVariableNames()[i++], val); - } - } - final Stack stack = new Stack(); - for (final Token t : getTokens()) { - ((CalculationToken) t).mutateStackForCalculation(stack, variableValues); - } - return stack.pop(); - } - - public void setVariable(String name, double value) { - variableValues.put(name, value); - } -} diff --git a/core/src/net/sf/openrocket/util/exp4j/Token.java b/core/src/net/sf/openrocket/util/exp4j/Token.java deleted file mode 100644 index 3e0024cc..00000000 --- a/core/src/net/sf/openrocket/util/exp4j/Token.java +++ /dev/null @@ -1,50 +0,0 @@ -/* - Copyright 2011 frank asseg - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - - */ -package net.sf.openrocket.util.exp4j; - -import java.util.Stack; - -/** - * Superclass for tokenized Strings - * - * @author fas@congrace.de - */ -abstract class Token { - private final String value; - - /** - * construct a new {@link Token} - * - * @param value - * the value of the {@link Token} - */ - Token(String value) { - super(); - this.value = value; - } - - /** - * get the value (String representation) of the token - * - * @return the value - */ - String getValue() { - return value; - } - - abstract void mutateStackForInfixTranslation(Stack operatorStack, StringBuilder output); -} diff --git a/core/src/net/sf/openrocket/util/exp4j/Tokenizer.java b/core/src/net/sf/openrocket/util/exp4j/Tokenizer.java deleted file mode 100644 index 8290de35..00000000 --- a/core/src/net/sf/openrocket/util/exp4j/Tokenizer.java +++ /dev/null @@ -1,219 +0,0 @@ -/* - Copyright 2011 frank asseg - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - - */ -package net.sf.openrocket.util.exp4j; - -import java.util.ArrayList; -import java.util.HashSet; -import java.util.List; -import java.util.Set; - -import net.sf.openrocket.util.exp4j.FunctionToken.Function; - -/** - * Class for tokenizing mathematical expressions by breaking an expression up - * into multiple different {@link Token}s - * - * @author fas@congrace.de - */ -class Tokenizer { - private String[] variableNames; - private final Set functionNames = new HashSet(); - private final Set customFunctions; - - { - functionNames.add("abs"); - functionNames.add("acos"); - functionNames.add("asin"); - functionNames.add("atan"); - functionNames.add("cbrt"); - functionNames.add("ceil"); - functionNames.add("cos"); - functionNames.add("cosh"); - functionNames.add("exp"); - functionNames.add("expm1"); - functionNames.add("floor"); - functionNames.add("log"); - functionNames.add("sin"); - functionNames.add("sinh"); - functionNames.add("sqrt"); - functionNames.add("tan"); - functionNames.add("tanh"); - } - - Tokenizer() { - super(); - customFunctions = null; - } - - /** - * construct a new Tokenizer that recognizes variable names - * - * @param variableNames - * the variable names in the expression - * @throws IllegalArgumentException - * if a variable has the name as a function - * @param customFunctions - * the CustomFunction implementations used if the variableNames - * are not valid - */ - Tokenizer(String[] variableNames, Set customFunctions) throws IllegalArgumentException { - super(); - this.variableNames = variableNames; - if (variableNames != null) { - for (String varName : variableNames) { - if (functionNames.contains(varName.toLowerCase())) { - throw new IllegalArgumentException("Variable '" + varName + "' can not have the same name as a function"); - } - } - } - this.customFunctions = customFunctions; - } - - private Token getCustomFunctionToken(String name) throws UnknownFunctionException { - for (CustomFunction func : customFunctions) { - if (func.getValue().equals(name)) { - return func; - } - } - throw new UnknownFunctionException(name); - } - - private boolean isCustomFunction(String name) { - if (customFunctions == null) { - return false; - } - for (CustomFunction func : customFunctions) { - if (func.getValue().equals(name)) { - return true; - } - } - return false; - } - - /** - * check if a char is part of a number - * - * @param c - * the char to be checked - * @return true if the char is part of a number - */ - private boolean isDigit(char c) { - return Character.isDigit(c) || c == '.'; - } - - private boolean isFunction(String name) { - for (Function fn : Function.values()) { - if (fn.name().equals(name.toUpperCase())) { - return true; - } - } - return false; - } - - /** - * check if a String is a variable name - * - * @param name - * the variable name which is checked to be valid the char to be - * checked - * @return true if the char is a variable name (e.g. x) - */ - private boolean isVariable(String name) { - if (variableNames != null) { - for (String var : variableNames) { - if (name.equals(var)) { - return true; - } - } - } - return false; - } - - /** - * tokenize an infix expression by breaking it up into different - * {@link Token} that can represent operations,functions,numbers, - * parenthesis or variables - * - * @param infix - * the infix expression to be tokenized - * @return the {@link Token}s representing the expression - * @throws UnparsableExpressionException - * when the expression is invalid - * @throws UnknownFunctionException - * when an unknown function name has been used. - */ - Token[] tokenize(String infix) throws UnparsableExpressionException, UnknownFunctionException { - final List tokens = new ArrayList(); - final char[] chars = infix.toCharArray(); - // iterate over the chars and fork on different types of input - Token lastToken; - for (int i = 0; i < chars.length; i++) { - char c = chars[i]; - if (c == ' ') - continue; - if (isDigit(c)) { - final StringBuilder valueBuilder = new StringBuilder(1); - // handle the numbers of the expression - valueBuilder.append(c); - int numberLen = 1; - while (chars.length > i + numberLen && isDigit(chars[i + numberLen])) { - valueBuilder.append(chars[i + numberLen]); - numberLen++; - } - i += numberLen - 1; - lastToken = new NumberToken(valueBuilder.toString()); - } else if (Character.isLetter(c) || c == '_') { - // can be a variable or function - final StringBuilder nameBuilder = new StringBuilder(); - nameBuilder.append(c); - int offset = 1; - while (chars.length > i + offset && (Character.isLetter(chars[i + offset]) || Character.isDigit(chars[i + offset]) || chars[i + offset] == '_')) { - nameBuilder.append(chars[i + offset++]); - } - String name = nameBuilder.toString(); - if (this.isVariable(name)) { - // a variable - i += offset - 1; - lastToken = new VariableToken(name); - } else if (this.isFunction(name)) { - // might be a function - i += offset - 1; - lastToken = new FunctionToken(name); - } else if (this.isCustomFunction(name)) { - // a custom function - i += offset - 1; - lastToken = getCustomFunctionToken(name); - } else { - // an unknown symbol was encountered - throw new UnparsableExpressionException(c, i); - } - }else if (c == ',') { - // a function separator, hopefully - lastToken=new FunctionSeparatorToken(); - } else if (OperatorToken.isOperator(c)) { - lastToken = new OperatorToken(String.valueOf(c), OperatorToken.getOperation(c)); - } else if (c == '(' || c == ')' || c == '[' || c == ']' || c == '{' || c == '}') { - lastToken = new ParenthesisToken(String.valueOf(c)); - } else { - // an unknown symbol was encountered - throw new UnparsableExpressionException(c, i); - } - tokens.add(lastToken); - } - return tokens.toArray(new Token[tokens.size()]); - } -} diff --git a/core/src/net/sf/openrocket/util/exp4j/UnknownFunctionException.java b/core/src/net/sf/openrocket/util/exp4j/UnknownFunctionException.java deleted file mode 100644 index a7a6beb9..00000000 --- a/core/src/net/sf/openrocket/util/exp4j/UnknownFunctionException.java +++ /dev/null @@ -1,37 +0,0 @@ -/* - Copyright 2011 frank asseg - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - - */ -package net.sf.openrocket.util.exp4j; - -/** - * Exception for handling unknown Functions. - * - * @see FunctionToken - * @author fas@congrace.de - */ -public class UnknownFunctionException extends Exception { - private static final long serialVersionUID = 1L; - - /** - * construct a new {@link UnknownFunctionException} - * - * @param functionName - * the function name which could not be found - */ - public UnknownFunctionException(String functionName) { - super("Unknown function: " + functionName); - } -} diff --git a/core/src/net/sf/openrocket/util/exp4j/UnparsableExpressionException.java b/core/src/net/sf/openrocket/util/exp4j/UnparsableExpressionException.java deleted file mode 100644 index deddcb9b..00000000 --- a/core/src/net/sf/openrocket/util/exp4j/UnparsableExpressionException.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - Copyright 2011 frank asseg - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - - */ -package net.sf.openrocket.util.exp4j; - -/** - * Exception for invalid expressions - * - * @author fas@congrace.de - */ -public class UnparsableExpressionException extends Exception { - private static final long serialVersionUID = 1L; - - /** - * construct a new {@link UnparsableExpressionException} - * - * @param c - * the character which could not be parsed - * @param pos - * the position of the character in the expression - */ - public UnparsableExpressionException(char c, int pos) { - super("Unable to parse character at position " + pos + ": '" + String.valueOf(c) + "'"); - } - /** - * construct a new {@link UnparsableExpressionException} - * - * @param msg - * the error message - */ - public UnparsableExpressionException(String msg) { - super(msg); - } -} diff --git a/core/src/net/sf/openrocket/util/exp4j/VariableToken.java b/core/src/net/sf/openrocket/util/exp4j/VariableToken.java deleted file mode 100644 index 1d6a336e..00000000 --- a/core/src/net/sf/openrocket/util/exp4j/VariableToken.java +++ /dev/null @@ -1,48 +0,0 @@ -/* - Copyright 2011 frank asseg - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - - */ -package net.sf.openrocket.util.exp4j; - -import java.util.Map; -import java.util.Stack; - -/** - * A {@link Token} for representing variables - * - * @author fas - */ -class VariableToken extends CalculationToken { - /** - * construct a new {@link VariableToken} - * - * @param value - * the value of the token - */ - VariableToken(String value) { - super(value); - } - - @Override - void mutateStackForCalculation(Stack stack, Map variableValues) { - double value = variableValues.get(this.getValue()); - stack.push(value); - } - - @Override - void mutateStackForInfixTranslation(Stack operatorStack, StringBuilder output) { - output.append(this.getValue()).append(" "); - } -} diff --git a/core/test/net/sf/openrocket/util/ExpressionParserTest.java b/core/test/net/sf/openrocket/util/ExpressionParserTest.java new file mode 100644 index 00000000..738f5b71 --- /dev/null +++ b/core/test/net/sf/openrocket/util/ExpressionParserTest.java @@ -0,0 +1,68 @@ +package net.sf.openrocket.util; + +import static org.junit.Assert.*; + +import org.junit.Test; + +public class ExpressionParserTest { + + private static final double EPS = 1e-10; + + private ExpressionParser parser = new ExpressionParser(); + + @Test + public void testPlainNumber() throws InvalidExpressionException { + assertEquals(1.0, parser.parse("1"), EPS); + assertEquals(1.0, parser.parse("\t 1 "), EPS); + assertEquals(0.9, parser.parse(".9"), EPS); + assertEquals(1.0, parser.parse("1."), EPS); + assertEquals(1.2, parser.parse("1.2"), EPS); + assertEquals(1.2, parser.parse("01.200"), EPS); + } + + + @Test + public void testDecimalComma() throws InvalidExpressionException { + assertEquals(1.0, parser.parse("1,"), EPS); + assertEquals(1.2, parser.parse("1,2"), EPS); + assertEquals(1.2, parser.parse("01,200"), EPS); + assertEquals(0.9, parser.parse(",9"), EPS); + } + + + @Test + public void testSimpleExpression() throws InvalidExpressionException { + assertEquals(3.0, parser.parse("1+2"), EPS); + assertEquals(6.0, parser.parse("1+2.5*2"), EPS); + assertEquals(7.0, parser.parse("(1+2.5) * 2"), EPS); + assertEquals(1.0 + 2.0 / 3.0, parser.parse("1+2/3"), EPS); + } + + @Test + public void testFraction() throws InvalidExpressionException { + assertEquals(1.5, parser.parse("1 1/2"), EPS); + assertEquals(1.5, parser.parse(" 1 1 / 2"), EPS); + assertEquals(2.0 + 3.0 / 7.0, parser.parse("1 + 1 3/7"), EPS); + assertEquals(3.0, parser.parse("1 1/2 * 2"), EPS); + } + + @Test + public void testInvalidExpression() { + expectInvalid("1+"); + expectInvalid("1+2/"); + expectInvalid("1 2"); + expectInvalid("12 2.5"); + expectInvalid("1 2.5/4"); + expectInvalid("1. 2"); + expectInvalid("1 .2"); + } + + private void expectInvalid(String exp) { + try { + double value = parser.parse(exp); + fail("Expression '" + exp + "' evaluated to " + value + ", expected failure"); + } catch (InvalidExpressionException e) { + // expected + } + } +}