diff --git a/.classpath b/.classpath index a98ed5b2f..b97a4aa38 100644 --- a/.classpath +++ b/.classpath @@ -4,10 +4,10 @@ - - - + + + diff --git a/ChangeLog b/ChangeLog index 91949f532..5967b47b0 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,20 @@ +2009-11-24 Sampo Niskanen + + * Released version 0.9.4 + +2009-11-24 Sampo Niskanen + + * Close original window when opening example design + +2009-11-10 Sampo Niskanen + + * [BUG] Fixed transition volume/mass computation + * [BUG] Simulations etc. using removed motor configuration IDs + +2009-10-11 Sampo Niskanen + + * [BUG] Sorting motor selection dialog with ',' decimal separator + 2009-10-10 Sampo Niskanen * Removed non-ASCII characters from source code files diff --git a/ReleaseNotes b/ReleaseNotes index 0120d5009..698f5ca4f 100644 --- a/ReleaseNotes +++ b/ReleaseNotes @@ -1,4 +1,12 @@ +OpenRocket 0.9.4 (2009-11-24): +------------------------------- + +Added through-the-wall fin tabs, attaching components to tube +couplers, material editing and automatic update checks, and fixed +numerous of the most commonly occurring bugs. + + OpenRocket 0.9.3 (2009-09-01): ------------------------------- diff --git a/TODO b/TODO index 9fff6762e..67f030c71 100644 --- a/TODO +++ b/TODO @@ -5,21 +5,28 @@ Feature roadmap for OpenRocket 1.0 Must-have: - Go through thrust curves and correct errors -- Add styrofoam and depron materials +or +- Hide duplicate motors Bugs: -- Unit tests fail from ant script Maybe: -- Inform user about software updates +- Re-investigate 15% reduction of three-fin CNa +- Take into account all fins in interference effects +- Add slight randomness to yaw moment Postponed: +- Integration with thrustcurve.org (syncing?) +- Reading thrust curves from external directory +- Plot motor thrust curve + + - Windows executable wrapper (launch4j) - Allow only one instance of OpenRocket running (RMI communication) - Only schedule rocket figure update instead of each time updating it @@ -30,7 +37,6 @@ Postponed: - Simulate other branches - Implement setDefaults() method for RocketComponent - BUG: Inner tube cluster rotation, edit with spinner arrows, slider wrong -- Reading thrust curves from external directory - NAR/CNES/etc competition validity checking - Running from command line - Print support @@ -79,4 +85,6 @@ In 0.9.4: - Allow editing user-defined materials - [BUG] All configuration dialogs too high - Simulation plot dialog forces dialog one button row too high (All/None) +- Add styrofoam and depron materials +- Inform user about software updates diff --git a/build.properties b/build.properties index 16dafdf9e..14052b6bd 100644 --- a/build.properties +++ b/build.properties @@ -1,7 +1,7 @@ # The OpenRocket build version -build.version=0.9.4pre +build.version=0.9.4 # The source of the package. When building a package for a specific diff --git a/datafiles/thrustcurves/SF_A8.eng b/datafiles/thrustcurves/SF_A8.eng index 6c9c9081b..292f5e34e 100644 --- a/datafiles/thrustcurves/SF_A8.eng +++ b/datafiles/thrustcurves/SF_A8.eng @@ -1,7 +1,8 @@ ; Sachsen Feuerwerk / WECO Feuerwerk A8-3 -; Created by Sampo Niskanen -; Data taken from: +; Created by Sampo Niskanen for OpenRocket, released into the Public Domain +; Thrust curve data taken from: ; http://www.raketenmodellbautechnik.de/produkte/Motoren/SF-Motoren.pdf +; Mass measured by the author. A8 18 70 3 0.00312 0.0153 SF 0.065 0.44 0.11 1.832 diff --git a/datafiles/thrustcurves/SF_B4.eng b/datafiles/thrustcurves/SF_B4.eng index 1a0db3464..e8a4adf88 100644 --- a/datafiles/thrustcurves/SF_B4.eng +++ b/datafiles/thrustcurves/SF_B4.eng @@ -1,7 +1,8 @@ ; Sachsen Feuerwerk / WECO Feuerwerk B4-0, B4-4 -; Created by Sampo Niskanen -; Data taken from: +; Created by Sampo Niskanen for OpenRocket, released into the Public Domain +; Thrust curve data taken from: ; http://www.raketenmodellbautechnik.de/produkte/Motoren/SF-Motoren.pdf +; Mass measured by the author from B4-4 motors. B4 18 70 0-4 0.00833 0.0195 SF 0.088 0.542 0.167 3.007 diff --git a/datafiles/thrustcurves/SF_C2.eng b/datafiles/thrustcurves/SF_C2.eng index 4698c6dd1..8e0b6e6eb 100644 --- a/datafiles/thrustcurves/SF_C2.eng +++ b/datafiles/thrustcurves/SF_C2.eng @@ -1,8 +1,8 @@ ; Sachsen Feuerwerk / WECO Feuerwerk Held 1000 -; Created by Sampo Niskanen -; True propellant weight unknown -; Data taken from: +; Created by Sampo Niskanen for OpenRocket, released into the Public Domain +; Thrust curve data taken from: ; http://www.raketenmodellbautechnik.de/produkte/Motoren/SF-Motoren.pdf +; True propellant weight unknown, estimates from various sources C2 15 95 P 0.012 0.024 SF 0.075 3.543 0.16 8.231 diff --git a/datafiles/thrustcurves/SF_C6.eng b/datafiles/thrustcurves/SF_C6.eng index 323945168..00da30e32 100644 --- a/datafiles/thrustcurves/SF_C6.eng +++ b/datafiles/thrustcurves/SF_C6.eng @@ -1,7 +1,8 @@ ; Sachsen Feuerwerk / WECO Feuerwerk C6-0, C6-3, C6-5 -; Created by Sampo Niskanen -; Data taken from: +; Created by Sampo Niskanen for OpenRocket, released into the Public Domain +; Thrust curve data taken from: ; http://www.raketenmodellbautechnik.de/produkte/Motoren/SF-Motoren.pdf +; Mass measured by the author from C6-3 motors. C6 18 70 0-3-5 0.01248 0.022 SF 0.096 0.579 0.152 2.441 diff --git a/datafiles/thrustcurves/SF_D7.eng b/datafiles/thrustcurves/SF_D7.eng index 9b424fb81..1a7dfe024 100644 --- a/datafiles/thrustcurves/SF_D7.eng +++ b/datafiles/thrustcurves/SF_D7.eng @@ -1,7 +1,8 @@ ; Sachsen Feuerwerk / WECO Feuerwerk D7-0, D7-3 -; Created by Sampo Niskanen -; Data taken from: +; Created by Sampo Niskanen for OpenRocket, released into the Public Domain +; Thrust curve data taken from: ; http://www.raketenmodellbautechnik.de/produkte/Motoren/SF-Motoren.pdf +; Mass measured by the author from D7-3 motors. D7 25 70 0-3 0.019 0.043 SF 0.079 1.625 0.179 6.979 diff --git a/dists/OpenRocket-0.9.4-src.zip b/dists/OpenRocket-0.9.4-src.zip new file mode 100644 index 000000000..7643ff4f0 Binary files /dev/null and b/dists/OpenRocket-0.9.4-src.zip differ diff --git a/dists/OpenRocket-0.9.4.jar b/dists/OpenRocket-0.9.4.jar new file mode 100644 index 000000000..6220f7161 Binary files /dev/null and b/dists/OpenRocket-0.9.4.jar differ diff --git a/html/actions/.htaccess b/html/actions/.htaccess index 6777c7e83..d18e09c2e 100644 --- a/html/actions/.htaccess +++ b/html/actions/.htaccess @@ -8,3 +8,7 @@ Redirect 307 /actions/reportbug http://sampo.kapsi.fi/openrocket/reportbug.php +RewriteEngine On +RewriteBase /actions/ +RewriteRule ^updates$ updates.php + diff --git a/html/actions/reportbug.php b/html/actions/reportbug.php index a6864a28c..86b8d8cf2 100644 --- a/html/actions/reportbug.php +++ b/html/actions/reportbug.php @@ -31,8 +31,8 @@ header("Content-type: text/plain; charset=utf-8"); if (preg_match("/^[a-zA-Z0-9. -]{1,30}$/", $version) && strlen($content) > 0) { - if (mail($mailaddr, "Automatic bug report for OpenRocket " . $version, - $content . $headers, + $subject = date("Y-m-d H:i:s") . " Automatic bug report for OpenRocket " . $version; + if (mail($mailaddr, $subject, $content . $headers, "From: Automatic Bug Reports <".$mailaddr.">\r\n". "Content-Type: text/plain; charset=utf-8")) { diff --git a/html/actions/updates.php b/html/actions/updates.php new file mode 100644 index 000000000..e49814501 --- /dev/null +++ b/html/actions/updates.php @@ -0,0 +1,65 @@ + $value) { + if (substr($name, 0, 5) == 'HTTP_') { + $headers[str_replace(' ', '-', ucwords(strtolower(str_replace('_', ' ', substr($name, 5)))))] = $value; + } + } + return $headers; + } +} + + +// Parse + validate headers +$orid = ""; +$orversion = ""; +$oros = ""; +$orjava = ""; +$orcountry = ""; +foreach (getallheaders() as $header => $value) { + if (preg_match("/^[a-zA-Z0-9 !$%&()*+,.\\/:=?@_~-]{1,40}$/", $value)) { + $h = strtolower($header); + if ($h == 'x-openrocket-version') { + $orversion = $value; + } else if ($h == 'x-openrocket-id') { + $orid = $value; + } else if ($h == 'x-openrocket-os') { + $oros = $value; + } else if ($h == 'x-openrocket-java') { + $orjava = $value; + } else if ($h == 'x-openrocket-country') { + $orcountry = $value; + } + } +} + +// Log the request +if (strlen($orversion) > 0 || strlen($orid) > 0 || strlen($oros) > 0 + || strlen($orjava) > 0 || strlen($orcountry) > 0) { + + $file = $logfiles . gmdate("Y-m"); + $line = gmdate("Y-m-d H:i:s") . ";" . $orid . ";" . $orversion . + ";" . $oros . ";" . $orjava . ";" . $orcountry . "\n"; + + $fp = fopen($file, 'a'); + if ($fp != FALSE) { + fwrite($fp, $line); + fclose($fp); + } +} + + +// Set HTTP content-type header +header("Content-type: text/plain; charset=utf-8"); + +$version = $_GET["version"]; + +// No updates available +header("HTTP/1.0 202 No Content"); + +?> \ No newline at end of file diff --git a/src/net/sf/openrocket/database/Databases.java b/src/net/sf/openrocket/database/Databases.java index d5d7f4651..cdf39c2e4 100644 --- a/src/net/sf/openrocket/database/Databases.java +++ b/src/net/sf/openrocket/database/Databases.java @@ -90,8 +90,9 @@ public class Databases { BULK_MATERIAL.add(new Material.Bulk("Polystyrene", 1050, false)); BULK_MATERIAL.add(new Material.Bulk("PVC", 1390, false)); BULK_MATERIAL.add(new Material.Bulk("Spruce", 450, false)); - BULK_MATERIAL.add(new Material.Bulk("Styrofoam generic (EPS)", 20, false)); - BULK_MATERIAL.add(new Material.Bulk("Styrofoam / Blue Foam (XPS)", 32, false)); + BULK_MATERIAL.add(new Material.Bulk("Styrofoam (generic EPS)", 20, false)); +// BULK_MATERIAL.add(new Material.Bulk("Styrofoam (Blue foam, XPS)", 32, false)); + BULK_MATERIAL.add(new Material.Bulk("Styrofoam \"Blue foam\" (XPS)", 32, false)); BULK_MATERIAL.add(new Material.Bulk("Quantum tubing",1050, false)); SURFACE_MATERIAL.add(new Material.Surface("Ripstop nylon", 0.067, false)); diff --git a/src/net/sf/openrocket/file/RASPMotorLoader.java b/src/net/sf/openrocket/file/RASPMotorLoader.java index 558e7cd8e..409d2188e 100644 --- a/src/net/sf/openrocket/file/RASPMotorLoader.java +++ b/src/net/sf/openrocket/file/RASPMotorLoader.java @@ -10,7 +10,9 @@ import java.util.List; import net.sf.openrocket.motor.Manufacturer; import net.sf.openrocket.motor.Motor; +import net.sf.openrocket.motor.MotorDigest; import net.sf.openrocket.motor.ThrustCurveMotor; +import net.sf.openrocket.motor.MotorDigest.DataType; import net.sf.openrocket.util.Coordinate; public class RASPMotorLoader extends MotorLoader { @@ -195,11 +197,19 @@ public class RASPMotorLoader extends MotorLoader { designation = removeDelay(designation); + // Create the motor digest from data available in RASP files + MotorDigest motorDigest = new MotorDigest(); + motorDigest.update(DataType.TIME_ARRAY, timeArray); + motorDigest.update(DataType.MASS_SPECIFIC, totalW, totalW-propW); + motorDigest.update(DataType.FORCE_PER_TIME, thrustArray); + final String digest = motorDigest.getDigest(); + + try { return new ThrustCurveMotor(Manufacturer.getManufacturer(manufacturer), designation, comment, Motor.Type.UNKNOWN, - delays, diameter, length, timeArray, thrustArray, cgArray); + delays, diameter, length, timeArray, thrustArray, cgArray, digest); } catch (IllegalArgumentException e) { diff --git a/src/net/sf/openrocket/file/RockSimMotorLoader.java b/src/net/sf/openrocket/file/RockSimMotorLoader.java index b39a9218e..48a15906b 100644 --- a/src/net/sf/openrocket/file/RockSimMotorLoader.java +++ b/src/net/sf/openrocket/file/RockSimMotorLoader.java @@ -14,7 +14,9 @@ import net.sf.openrocket.file.simplesax.PlainTextHandler; import net.sf.openrocket.file.simplesax.SimpleSAX; import net.sf.openrocket.motor.Manufacturer; import net.sf.openrocket.motor.Motor; +import net.sf.openrocket.motor.MotorDigest; import net.sf.openrocket.motor.ThrustCurveMotor; +import net.sf.openrocket.motor.MotorDigest.DataType; import net.sf.openrocket.util.Coordinate; import org.xml.sax.InputSource; @@ -321,6 +323,11 @@ public class RockSimMotorLoader extends MotorLoader { finalizeThrustCurve(time, force, mass, cg); final int n = time.size(); + if (hasIllegalValue(mass)) + calculateMass = true; + if (hasIllegalValue(cg)) + calculateCG = true; + if (calculateMass) { mass = calculateMass(time, force, initMass, propMass); } @@ -330,19 +337,33 @@ public class RockSimMotorLoader extends MotorLoader { } } - double[] timeArray = new double[n]; - double[] thrustArray = new double[n]; + double[] timeArray = toArray(time); + double[] thrustArray = toArray(force); Coordinate[] cgArray = new Coordinate[n]; for (int i=0; i < n; i++) { - timeArray[i] = time.get(i); - thrustArray[i] = force.get(i); cgArray[i] = new Coordinate(cg.get(i),0,0,mass.get(i)); } + + // Create the motor digest from all data available in the file + MotorDigest motorDigest = new MotorDigest(); + motorDigest.update(DataType.TIME_ARRAY, timeArray); + if (!calculateMass) { + motorDigest.update(DataType.MASS_PER_TIME, toArray(mass)); + } else { + motorDigest.update(DataType.MASS_SPECIFIC, initMass, initMass-propMass); + } + if (!calculateCG) { + motorDigest.update(DataType.CG_PER_TIME, toArray(cg)); + } + motorDigest.update(DataType.FORCE_PER_TIME, thrustArray); + final String digest = motorDigest.getDigest(); + + try { return new ThrustCurveMotor(Manufacturer.getManufacturer(manufacturer), designation, description, type, - delays, diameter, length, timeArray, thrustArray, cgArray); + delays, diameter, length, timeArray, thrustArray, cgArray, digest); } catch (IllegalArgumentException e) { throw new SAXException("Illegal motor data", e); } @@ -417,4 +438,24 @@ public class RockSimMotorLoader extends MotorLoader { } } } + + + + private static boolean hasIllegalValue(List list) { + for (Double d: list) { + if (d == null || d.isNaN() || d.isInfinite()) { + return true; + } + } + return false; + } + + private static double[] toArray(List list) { + final int n = list.size(); + double[] array = new double[n]; + for (int i=0; i < n; i++) { + array[i] = list.get(i); + } + return array; + } } diff --git a/src/net/sf/openrocket/gui/adaptors/DoubleModel.java b/src/net/sf/openrocket/gui/adaptors/DoubleModel.java index b39cb0104..da0c7747b 100644 --- a/src/net/sf/openrocket/gui/adaptors/DoubleModel.java +++ b/src/net/sf/openrocket/gui/adaptors/DoubleModel.java @@ -379,16 +379,17 @@ public class DoubleModel implements ChangeListener, ChangeSource { } // Implement a wrapper to the ChangeListeners - ArrayList listeners = new ArrayList(); + ArrayList propertyChangeListeners = + new ArrayList(); @Override public void addPropertyChangeListener(PropertyChangeListener listener) { - listeners.add(listener); + propertyChangeListeners.add(listener); DoubleModel.this.addChangeListener(this); } @Override public void removePropertyChangeListener(PropertyChangeListener listener) { - listeners.remove(listener); - if (listeners.isEmpty()) + propertyChangeListeners.remove(listener); + if (propertyChangeListeners.isEmpty()) DoubleModel.this.removeChangeListener(this); } // If the value has changed, generate an event to the listeners @@ -399,7 +400,7 @@ public class DoubleModel implements ChangeListener, ChangeSource { PropertyChangeEvent event = new PropertyChangeEvent(this,Action.SELECTED_KEY, oldValue,newValue); oldValue = newValue; - Object[] l = listeners.toArray(); + Object[] l = propertyChangeListeners.toArray(); for (int i=0; i> extends AbstractListModel } if (!(item instanceof Enum)) { - throw new IllegalArgumentException("Not String or Enum"); + throw new IllegalArgumentException("Not String or Enum, item="+item); } // Comparison with == ok, since both are enums diff --git a/src/net/sf/openrocket/gui/configdialog/FreeformFinSetConfig.java b/src/net/sf/openrocket/gui/configdialog/FreeformFinSetConfig.java index 52e02686a..369b09b6b 100644 --- a/src/net/sf/openrocket/gui/configdialog/FreeformFinSetConfig.java +++ b/src/net/sf/openrocket/gui/configdialog/FreeformFinSetConfig.java @@ -450,6 +450,12 @@ public class FreeformFinSetConfig extends FinSetConfig { if (!(o instanceof String)) return; + if (rowIndex < 0 || rowIndex >= finset.getFinPoints().length || + columnIndex < 0 || columnIndex >= Columns.values().length) { + throw new IllegalArgumentException("Index out of bounds, row="+rowIndex+ + " column="+columnIndex+" fin point count="+finset.getFinPoints().length); + } + String str = (String)o; try { @@ -466,7 +472,5 @@ public class FreeformFinSetConfig extends FinSetConfig { } catch (IllegalFinPointException ignore) { } } - - } } diff --git a/src/net/sf/openrocket/gui/configdialog/InnerTubeConfig.java b/src/net/sf/openrocket/gui/configdialog/InnerTubeConfig.java index 427868359..691fcf7f1 100644 --- a/src/net/sf/openrocket/gui/configdialog/InnerTubeConfig.java +++ b/src/net/sf/openrocket/gui/configdialog/InnerTubeConfig.java @@ -50,14 +50,14 @@ public class InnerTubeConfig extends ThicknessRingComponentConfig { JPanel tab; - tab = positionTab(); - tabbedPane.insertTab("Radial position", null, tab, "Radial position", 1); - tab = new MotorConfig((MotorMount)c); - tabbedPane.insertTab("Motor", null, tab, "Motor mount configuration", 2); + tabbedPane.insertTab("Motor", null, tab, "Motor mount configuration", 1); tab = clusterTab(); - tabbedPane.insertTab("Cluster", null, tab, "Cluster configuration", 3); + tabbedPane.insertTab("Cluster", null, tab, "Cluster configuration", 2); + + tab = positionTab(); + tabbedPane.insertTab("Radial position", null, tab, "Radial position", 3); tabbedPane.setSelectedIndex(0); } diff --git a/src/net/sf/openrocket/gui/dialogs/BugReportDialog.java b/src/net/sf/openrocket/gui/dialogs/BugReportDialog.java index 9ebd9f2a8..b5b44b103 100644 --- a/src/net/sf/openrocket/gui/dialogs/BugReportDialog.java +++ b/src/net/sf/openrocket/gui/dialogs/BugReportDialog.java @@ -26,8 +26,8 @@ import javax.swing.JTextArea; import net.miginfocom.swing.MigLayout; import net.sf.openrocket.communication.BugReporter; -import net.sf.openrocket.gui.components.StyledLabel; import net.sf.openrocket.gui.components.SelectableLabel; +import net.sf.openrocket.gui.components.StyledLabel; import net.sf.openrocket.util.GUIUtil; import net.sf.openrocket.util.JarUtil; import net.sf.openrocket.util.Prefs; @@ -179,8 +179,8 @@ public class BugReportDialog extends JDialog { BugReportDialog reportDialog = new BugReportDialog(parent, - "You can report a bug in OpenRocket by filling in and submitting " + - "the form below.
" + + "You can report a bug in OpenRocket by filling in and submitting " + + "the form below.
" + "You can also report bugs and include attachments on the project " + "web site.", sb.toString()); reportDialog.setVisible(true); @@ -239,8 +239,8 @@ public class BugReportDialog extends JDialog { sb.append('\n'); BugReportDialog reportDialog = - new BugReportDialog(parent, "Please include a short description about " + - "what you were doing when the exception occurred.", sb.toString()); + new BugReportDialog(parent, "Please include a short description about " + + "what you were doing when the exception occurred.", sb.toString()); reportDialog.setVisible(true); } diff --git a/src/net/sf/openrocket/gui/dialogs/MotorChooserDialog.java b/src/net/sf/openrocket/gui/dialogs/MotorChooserDialog.java index 887085df9..0aba355f9 100644 --- a/src/net/sf/openrocket/gui/dialogs/MotorChooserDialog.java +++ b/src/net/sf/openrocket/gui/dialogs/MotorChooserDialog.java @@ -12,9 +12,6 @@ import java.awt.event.MouseEvent; import java.text.Collator; import java.util.ArrayList; import java.util.Comparator; -import java.util.Locale; -import java.util.regex.Matcher; -import java.util.regex.Pattern; import javax.swing.DefaultComboBoxModel; import javax.swing.JButton; @@ -40,6 +37,8 @@ import net.sf.openrocket.database.Databases; import net.sf.openrocket.gui.components.StyledLabel; import net.sf.openrocket.motor.Motor; import net.sf.openrocket.unit.UnitGroup; +import net.sf.openrocket.unit.Value; +import net.sf.openrocket.unit.ValueComparator; import net.sf.openrocket.util.GUIUtil; import net.sf.openrocket.util.Prefs; @@ -383,10 +382,6 @@ public class MotorChooserDialog extends JDialog { public String getValue(Motor m) { return m.getManufacturer().getDisplayName(); } -// @Override -// public String getToolTipText(Motor m) { -// return "" + m.getDescription().replace((CharSequence)"\n", "
"); -// } @Override public Comparator getComparator() { return Collator.getInstance(); @@ -397,10 +392,6 @@ public class MotorChooserDialog extends JDialog { public String getValue(Motor m) { return m.getDesignation(); } -// @Override -// public String getToolTipText(Motor m) { -// return "" + m.getDescription().replace((CharSequence)"\n", "
"); -// } @Override public Comparator getComparator() { return Motor.getDesignationComparator(); @@ -411,10 +402,6 @@ public class MotorChooserDialog extends JDialog { public String getValue(Motor m) { return m.getMotorType().getName(); } -// @Override -// public String getToolTipText(Motor m) { -// return m.getMotorType().getDescription(); -// } @Override public Comparator getComparator() { return Collator.getInstance(); @@ -422,46 +409,42 @@ public class MotorChooserDialog extends JDialog { }, DIAMETER("Diameter") { @Override - public String getValue(Motor m) { - return UnitGroup.UNITS_MOTOR_DIMENSIONS.getDefaultUnit().toStringUnit( - m.getDiameter()); + public Object getValue(Motor m) { + return new Value(m.getDiameter(), UnitGroup.UNITS_MOTOR_DIMENSIONS); } @Override public Comparator getComparator() { - return getNumericalComparator(); + return ValueComparator.INSTANCE; } }, LENGTH("Length") { @Override - public String getValue(Motor m) { - return UnitGroup.UNITS_MOTOR_DIMENSIONS.getDefaultUnit().toStringUnit( - m.getLength()); + public Object getValue(Motor m) { + return new Value(m.getLength(), UnitGroup.UNITS_MOTOR_DIMENSIONS); } @Override public Comparator getComparator() { - return getNumericalComparator(); + return ValueComparator.INSTANCE; } }, IMPULSE("Impulse") { @Override - public String getValue(Motor m) { - return UnitGroup.UNITS_IMPULSE.getDefaultUnit().toStringUnit( - m.getTotalImpulse()); + public Object getValue(Motor m) { + return new Value(m.getTotalImpulse(), UnitGroup.UNITS_IMPULSE); } @Override public Comparator getComparator() { - return getNumericalComparator(); + return ValueComparator.INSTANCE; } }, TIME("Burn time") { @Override - public String getValue(Motor m) { - return UnitGroup.UNITS_SHORT_TIME.getDefaultUnit().toStringUnit( - m.getAverageTime()); + public Object getValue(Motor m) { + return new Value(m.getAverageTime(), UnitGroup.UNITS_SHORT_TIME); } @Override public Comparator getComparator() { - return getNumericalComparator(); + return ValueComparator.INSTANCE; } }; @@ -479,7 +462,7 @@ public class MotorChooserDialog extends JDialog { } - public abstract String getValue(Motor m); + public abstract Object getValue(Motor m); public abstract Comparator getComparator(); public String getTitle() { @@ -623,7 +606,7 @@ public class MotorChooserDialog extends JDialog { public boolean filterByString(Motor m) { main: for (String s : searchTerms) { for (MotorColumns col : MotorColumns.values()) { - String str = col.getValue(m).toLowerCase(); + String str = col.getValue(m).toString().toLowerCase(); if (str.indexOf(s) >= 0) continue main; } @@ -664,35 +647,4 @@ public class MotorChooserDialog extends JDialog { } } - - private static Comparator numericalComparator = null; - private static Comparator getNumericalComparator() { - if (numericalComparator == null) - numericalComparator = new NumericalComparator(); - return numericalComparator; - } - - private static class NumericalComparator implements Comparator { - private Pattern pattern = - Pattern.compile("^\\s*([0-9]*[.,][0-9]+|[0-9]+[.,]?[0-9]*).*?$"); - private Collator collator = null; - @Override - public int compare(String s1, String s2) { - Matcher m1, m2; - - m1 = pattern.matcher(s1); - m2 = pattern.matcher(s2); - if (m1.find() && m2.find()) { - double d1 = Double.parseDouble(m1.group(1)); - double d2 = Double.parseDouble(m2.group(1)); - - return (int)((d1-d2)*1000); - } - - if (collator == null) - collator = Collator.getInstance(Locale.US); - return collator.compare(s1, s2); - } - } - } diff --git a/src/net/sf/openrocket/gui/dialogs/preferences/PreferencesDialog.java b/src/net/sf/openrocket/gui/dialogs/preferences/PreferencesDialog.java index e5f38260d..f2ed0ef2a 100644 --- a/src/net/sf/openrocket/gui/dialogs/preferences/PreferencesDialog.java +++ b/src/net/sf/openrocket/gui/dialogs/preferences/PreferencesDialog.java @@ -88,7 +88,7 @@ public class PreferencesDialog extends JDialog { "Delete", "Confirm", true)), "wrap 40lp, growx, sg combos"); - final JCheckBox softwareUpdateBox = new JCheckBox("Check for software updates"); + final JCheckBox softwareUpdateBox = new JCheckBox("Check for software updates at startup"); softwareUpdateBox.setSelected(Prefs.getCheckUpdates()); softwareUpdateBox.addActionListener(new ActionListener() { @Override diff --git a/src/net/sf/openrocket/gui/main/BasicFrame.java b/src/net/sf/openrocket/gui/main/BasicFrame.java index f7777a002..c2d0d3367 100644 --- a/src/net/sf/openrocket/gui/main/BasicFrame.java +++ b/src/net/sf/openrocket/gui/main/BasicFrame.java @@ -752,7 +752,7 @@ public class BasicFrame extends JFrame { - private static boolean open(URL url, Window parent) { + private static boolean open(URL url, BasicFrame parent) { String filename = null; // Try using URI.getPath(); @@ -780,7 +780,13 @@ public class BasicFrame extends JFrame { try { InputStream is = url.openStream(); - open(is, filename, parent); + if (open(is, filename, parent)) { + // Close previous window if replacing + if (parent.replaceable && parent.document.isSaved()) { + parent.closeAction(); + parent.replaceable = false; + } + } } catch (IOException e) { JOptionPane.showMessageDialog(parent, "An error occurred while opening the file " + filename, diff --git a/src/net/sf/openrocket/gui/main/ExceptionHandler.java b/src/net/sf/openrocket/gui/main/ExceptionHandler.java index 7b233b022..c1529a3c1 100644 --- a/src/net/sf/openrocket/gui/main/ExceptionHandler.java +++ b/src/net/sf/openrocket/gui/main/ExceptionHandler.java @@ -163,7 +163,7 @@ public class ExceptionHandler implements Thread.UncaughtExceptionHandler { // Normal exception, show question dialog String msg = e.getClass().getSimpleName() + ": " + e.getMessage(); if (msg.length() > 90) { - msg = msg.substring(0, 90) + "..."; + msg = msg.substring(0, 80) + "..."; } diff --git a/src/net/sf/openrocket/motor/Motor.java b/src/net/sf/openrocket/motor/Motor.java index 0da9c3b42..ce746d332 100644 --- a/src/net/sf/openrocket/motor/Motor.java +++ b/src/net/sf/openrocket/motor/Motor.java @@ -98,6 +98,7 @@ public abstract class Motor implements Comparable { private final String designation; private final String description; private final Type motorType; + private final String digest; private final double[] delays; @@ -126,7 +127,7 @@ public abstract class Motor implements Comparable { * @param length length of the motor */ protected Motor(Manufacturer manufacturer, String designation, String description, - Type type, double[] delays, double diameter, double length) { + Type type, double[] delays, double diameter, double length, String digest) { if (manufacturer == null || designation == null || description == null || type == null || delays == null) { @@ -140,6 +141,7 @@ public abstract class Motor implements Comparable { this.delays = delays.clone(); this.diameter = diameter; this.length = length; + this.digest = digest; } @@ -435,6 +437,20 @@ public abstract class Motor implements Comparable { } + /** + * Return a digest string of this motor. This digest should be computed from all + * flight-affecting data. For example for thrust curve motors the thrust curve + * should be digested using suitable precision. The intention is that the combination + * of motor type, manufacturer, designation, diameter, length and digest uniquely + * identify any particular motor data file. + * + * @return a string digest of this motor (0-60 chars) + */ + public String getDigestString() { + return digest; + } + + /** * Compares two Motor objects. The motors are considered equal * if they have identical manufacturers, designations and types, near-identical diff --git a/src/net/sf/openrocket/motor/MotorDigest.java b/src/net/sf/openrocket/motor/MotorDigest.java new file mode 100644 index 000000000..c6ec9394f --- /dev/null +++ b/src/net/sf/openrocket/motor/MotorDigest.java @@ -0,0 +1,141 @@ +package net.sf.openrocket.motor; + +import java.io.UnsupportedEncodingException; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; + +import net.sf.openrocket.util.TextUtil; + +public class MotorDigest { + + private static final double EPSILON = 0.00000000001; + + public enum DataType { + /** An array of time points at which data is available (in ms) */ + TIME_ARRAY(0, 1000), + /** Mass data for a few specific points (normally initial and empty mass) (in 0.1g) */ + MASS_SPECIFIC(1, 10000), + /** Mass per time (in 0.1g) */ + MASS_PER_TIME(2, 10000), + /** CG position for a few specific points (normally initial and final CG) (in mm) */ + CG_SPECIFIC(3, 1000), + /** CG position per time (in mm) */ + CG_PER_TIME(4, 1000), + /** Thrust force per time (in mN) */ + FORCE_PER_TIME(5, 1000); + + private final int order; + private final int multiplier; + DataType(int order, int multiplier) { + this.order = order; + this.multiplier = multiplier; + } + public int getOrder() { + return order; + } + public int getMultiplier() { + return multiplier; + } + } + + + private final MessageDigest digest; + private boolean used = false; + private int lastOrder = -1; + + + public MotorDigest() { + try { + digest = MessageDigest.getInstance("MD5"); + } catch (NoSuchAlgorithmException e) { + throw new IllegalStateException("MD5 digest not supported by JRE", e); + } + } + + + public void update(DataType type, int ... values) { + + // Check for correct order + if (lastOrder >= type.getOrder()) { + throw new IllegalArgumentException("Called with type="+type+" order="+type.getOrder()+ + " while lastOrder=" + lastOrder); + } + lastOrder = type.getOrder(); + + // Digest the type + digest.update(bytes(type.getOrder())); + + // Digest the data length + digest.update(bytes(values.length)); + + // Digest the values + for (int v: values) { + digest.update(bytes(v)); + } + + } + + + private void update(DataType type, int multiplier, double ... values) { + + int[] intValues = new int[values.length]; + for (int i=0; i>>24) & 0xFF), (byte) ((value>>>16) & 0xFF), + (byte) ((value>>>8) & 0xFF), (byte) (value & 0xFF) + }; + } + + + + + public static String digestComment(String comment) { + comment = comment.replaceAll("\\s+", " ").trim(); + + MessageDigest digest; + try { + digest = MessageDigest.getInstance("MD5"); + } catch (NoSuchAlgorithmException e) { + throw new IllegalStateException("MD5 digest not supported by JRE", e); + } + + try { + digest.update(comment.getBytes("UTF-8")); + } catch (UnsupportedEncodingException e) { + throw new IllegalStateException("UTF-8 encoding not supported by JRE", e); + } + + return TextUtil.hexString(digest.digest()); + } + +} diff --git a/src/net/sf/openrocket/motor/ThrustCurveMotor.java b/src/net/sf/openrocket/motor/ThrustCurveMotor.java index e495defeb..1ccfec20c 100644 --- a/src/net/sf/openrocket/motor/ThrustCurveMotor.java +++ b/src/net/sf/openrocket/motor/ThrustCurveMotor.java @@ -35,8 +35,8 @@ public class ThrustCurveMotor extends Motor { */ public ThrustCurveMotor(Manufacturer manufacturer, String designation, String description, Motor.Type type, double[] delays, double diameter, double length, - double[] time, double[] thrust, Coordinate[] cg) { - super(manufacturer, designation, description, type, delays, diameter, length); + double[] time, double[] thrust, Coordinate[] cg, String digest) { + super(manufacturer, designation, description, type, delays, diameter, length, digest); double max = -1; @@ -157,5 +157,5 @@ public class ThrustCurveMotor extends Motor { public Coordinate[] getCGPoints() { return cg.clone(); } - + } diff --git a/src/net/sf/openrocket/rocketcomponent/BodyTube.java b/src/net/sf/openrocket/rocketcomponent/BodyTube.java index d3368f1f1..05ee9eed9 100644 --- a/src/net/sf/openrocket/rocketcomponent/BodyTube.java +++ b/src/net/sf/openrocket/rocketcomponent/BodyTube.java @@ -290,11 +290,26 @@ public class BodyTube extends SymmetricComponent implements MotorMount { @Override public Motor getMotor(String id) { + if (id == null) + return null; + + // Check whether the id is valid for the current rocket + RocketComponent root = this.getRoot(); + if (!(root instanceof Rocket)) + return null; + if (!((Rocket) root).isMotorConfigurationID(id)) + return null; + return motors.get(id); } @Override public void setMotor(String id, Motor motor) { + if (id == null) { + if (motor != null) { + throw new IllegalArgumentException("Cannot set non-null motor for id null"); + } + } Motor current = motors.get(id); if ((motor == null && current == null) || (motor != null && motor.equals(current))) diff --git a/src/net/sf/openrocket/rocketcomponent/FreeformFinSet.java b/src/net/sf/openrocket/rocketcomponent/FreeformFinSet.java index d5275db88..05862f3d4 100644 --- a/src/net/sf/openrocket/rocketcomponent/FreeformFinSet.java +++ b/src/net/sf/openrocket/rocketcomponent/FreeformFinSet.java @@ -124,7 +124,7 @@ public class FreeformFinSet extends FinSet { /** * Remove the fin point with the given index. The first and last fin points - * cannot be removed, and will cause an IllegalArgumentException + * cannot be removed, and will cause an IllegalFinPointException * if attempted. * * @param index the fin point index to remove @@ -173,8 +173,8 @@ public class FreeformFinSet extends FinSet { *

* Note that this method enforces basic fin shape restrictions (non-negative y, * first and last point locations) silently, but throws an - * IllegalArgumentException if the point causes fin segments to - * intersect. The calling method should always catch this exception. + * IllegalFinPointException if the point causes fin segments to + * intersect. *

* Moving of the first point in the X-axis is allowed, but this actually moves * all of the other points the corresponding distance back. diff --git a/src/net/sf/openrocket/rocketcomponent/InnerTube.java b/src/net/sf/openrocket/rocketcomponent/InnerTube.java index 901b5cb60..0c30042b0 100644 --- a/src/net/sf/openrocket/rocketcomponent/InnerTube.java +++ b/src/net/sf/openrocket/rocketcomponent/InnerTube.java @@ -201,11 +201,26 @@ implements Clusterable, RadialParent, MotorMount { @Override public Motor getMotor(String id) { + if (id == null) + return null; + + // Check whether the id is valid for the current rocket + RocketComponent root = this.getRoot(); + if (!(root instanceof Rocket)) + return null; + if (!((Rocket) root).isMotorConfigurationID(id)) + return null; + return motors.get(id); } @Override public void setMotor(String id, Motor motor) { + if (id == null) { + if (motor != null) { + throw new IllegalArgumentException("Cannot set non-null motor for id null"); + } + } Motor current = motors.get(id); if ((motor == null && current == null) || (motor != null && motor.equals(current))) @@ -228,6 +243,7 @@ implements Clusterable, RadialParent, MotorMount { fireComponentChangeEvent(ComponentChangeEvent.MOTOR_CHANGE); } + @Deprecated @Override public int getMotorCount() { return getClusterCount(); diff --git a/src/net/sf/openrocket/rocketcomponent/MotorMount.java b/src/net/sf/openrocket/rocketcomponent/MotorMount.java index 20f3c82e9..6735ee742 100644 --- a/src/net/sf/openrocket/rocketcomponent/MotorMount.java +++ b/src/net/sf/openrocket/rocketcomponent/MotorMount.java @@ -88,7 +88,8 @@ public interface MotorMount extends ChangeSource { /** * Return the motor for the motor configuration. May return null * if no motor has been set. This method must return null if ID - * is null. + * is null or if the ID is not valid for the current rocket + * (or if the component is not part of any rocket). * * @param id the motor configuration ID * @return the motor, or null if not set. @@ -107,8 +108,11 @@ public interface MotorMount extends ChangeSource { /** * Get the number of similar motors clustered. * + * TODO: HIGH: This should not be used, since the components themselves can be clustered + * * @return the number of motors. */ + @Deprecated public int getMotorCount(); diff --git a/src/net/sf/openrocket/rocketcomponent/Rocket.java b/src/net/sf/openrocket/rocketcomponent/Rocket.java index 145e4cf04..9ab110b49 100644 --- a/src/net/sf/openrocket/rocketcomponent/Rocket.java +++ b/src/net/sf/openrocket/rocketcomponent/Rocket.java @@ -563,6 +563,9 @@ public class Rocket extends RocketComponent { * @return whether any motors are defined for it. */ public boolean hasMotors(String id) { + if (id == null) + return false; + Iterator iterator = this.deepIterator(); while (iterator.hasNext()) { RocketComponent c = iterator.next(); @@ -588,6 +591,8 @@ public class Rocket extends RocketComponent { * @return the configuration name */ public String getMotorConfigurationName(String id) { + if (!isMotorConfigurationID(id)) + return ""; String s = motorConfigurationNames.get(id); if (s == null) return ""; @@ -607,7 +612,7 @@ public class Rocket extends RocketComponent { fireComponentChangeEvent(ComponentChangeEvent.NONFUNCTIONAL_CHANGE); } - + /** * Return either the motor configuration name (if set) or its description. * @@ -617,10 +622,10 @@ public class Rocket extends RocketComponent { public String getMotorConfigurationNameOrDescription(String id) { String name; - name = motorConfigurationNames.get(id); + name = getMotorConfigurationName(id); if (name != null && !name.equals("")) return name; - + return getMotorConfigurationDescription(id); } @@ -636,10 +641,6 @@ public class Rocket extends RocketComponent { String name; int motorCount = 0; - if (!motorConfigurationIDs.contains(id)) { - throw new IllegalArgumentException("Motor configuration ID does not exist: "+id); - } - // Generate the description // First iterate over each stage and store the designations of each motor diff --git a/src/net/sf/openrocket/rocketcomponent/SymmetricComponent.java b/src/net/sf/openrocket/rocketcomponent/SymmetricComponent.java index ed0bffb6a..e25ba691d 100644 --- a/src/net/sf/openrocket/rocketcomponent/SymmetricComponent.java +++ b/src/net/sf/openrocket/rocketcomponent/SymmetricComponent.java @@ -308,7 +308,7 @@ public abstract class SymmetricComponent extends BodyComponent implements Radial } else { // Hollow piece final double height = thickness*hyp/l; - dV = pil*height*(r1+r2-height); + dV = MathUtil.max(pil*height*(r1+r2-height), 0); } // Add to the volume-related components @@ -334,11 +334,14 @@ public abstract class SymmetricComponent extends BodyComponent implements Radial if (planArea > 0) planCenter /= planArea; - if (volume == 0) { - cg = Coordinate.NUL; + if (volume < 0.0000000001) { // 0.1 mm^3 + volume = 0; + cg = new Coordinate(length/2, 0, 0, 0); } else { // getComponentMass is safe now - cg = new Coordinate(cgx/volume,0,0,getComponentMass()); + // Use super.getComponentMass() to ensure only the transition shape mass + // is used, not the shoulders + cg = new Coordinate(cgx/volume,0,0,super.getComponentMass()); } } diff --git a/src/net/sf/openrocket/rocketcomponent/Transition.java b/src/net/sf/openrocket/rocketcomponent/Transition.java index 5478545fe..f598ad1ee 100644 --- a/src/net/sf/openrocket/rocketcomponent/Transition.java +++ b/src/net/sf/openrocket/rocketcomponent/Transition.java @@ -171,6 +171,9 @@ public class Transition extends SymmetricComponent { } public void setType(Shape type) { + if (type == null) { + throw new IllegalArgumentException("BUG: setType called with null argument"); + } if (this.type == type) return; this.type = type; diff --git a/src/net/sf/openrocket/unit/FrequencyUnit.java b/src/net/sf/openrocket/unit/FrequencyUnit.java new file mode 100644 index 000000000..7b5d15cfb --- /dev/null +++ b/src/net/sf/openrocket/unit/FrequencyUnit.java @@ -0,0 +1,25 @@ +package net.sf.openrocket.unit; + +public class FrequencyUnit extends GeneralUnit { + + + public FrequencyUnit(double multiplier, String unit) { + super(multiplier, unit); + } + + + + @Override + public double toUnit(double value) { + double hz = 1/value; + return hz / multiplier; + } + + + @Override + public double fromUnit(double value) { + double hz = value * multiplier; + return 1/hz; + } + +} diff --git a/src/net/sf/openrocket/unit/UnitGroup.java b/src/net/sf/openrocket/unit/UnitGroup.java index ffe80f226..1d8848b27 100644 --- a/src/net/sf/openrocket/unit/UnitGroup.java +++ b/src/net/sf/openrocket/unit/UnitGroup.java @@ -57,6 +57,8 @@ public class UnitGroup { public static final UnitGroup UNITS_COEFFICIENT; +// public static final UnitGroup UNITS_FREQUENCY; + public static final Map UNITS; @@ -203,6 +205,16 @@ public class UnitGroup { UNITS_COEFFICIENT = new UnitGroup(); UNITS_COEFFICIENT.addUnit(new FixedPrecisionUnit(""+ZWSP, 0.01)); // zero-width space + + // This is not used by OpenRocket, and not extensively tested: +// UNITS_FREQUENCY = new UnitGroup(); +// UNITS_FREQUENCY.addUnit(new GeneralUnit(1, "s")); +// UNITS_FREQUENCY.addUnit(new GeneralUnit(0.001, "ms")); +// UNITS_FREQUENCY.addUnit(new GeneralUnit(0.000001, MICRO + "s")); +// UNITS_FREQUENCY.addUnit(new FrequencyUnit(1, "Hz")); +// UNITS_FREQUENCY.addUnit(new FrequencyUnit(1000, "kHz")); +// UNITS_FREQUENCY.setDefaultUnit(3); + HashMap map = new HashMap(); map.put("NONE", UNITS_NONE); diff --git a/src/net/sf/openrocket/unit/Value.java b/src/net/sf/openrocket/unit/Value.java index 54e192790..5b518673f 100644 --- a/src/net/sf/openrocket/unit/Value.java +++ b/src/net/sf/openrocket/unit/Value.java @@ -27,6 +27,18 @@ public class Value implements Comparable { this.value = value; this.unit = unit; } + + + /** + * Creates a new Value object using unit group. Currently it simply uses the default + * unit of the group, but may later change. + * + * @param value the value to set. + * @param group the group the value belongs to. + */ + public Value(double value, UnitGroup group) { + this(value, group.getDefaultUnit()); + } /** diff --git a/src/net/sf/openrocket/unit/ValueComparator.java b/src/net/sf/openrocket/unit/ValueComparator.java new file mode 100644 index 000000000..a948e9d2d --- /dev/null +++ b/src/net/sf/openrocket/unit/ValueComparator.java @@ -0,0 +1,14 @@ +package net.sf.openrocket.unit; + +import java.util.Comparator; + +public class ValueComparator implements Comparator { + + public static final ValueComparator INSTANCE = new ValueComparator(); + + @Override + public int compare(Value o1, Value o2) { + return o1.compareTo(o2); + } + +} diff --git a/src/net/sf/openrocket/util/Base64.java b/src/net/sf/openrocket/util/Base64.java index fb8490a79..042d36715 100644 --- a/src/net/sf/openrocket/util/Base64.java +++ b/src/net/sf/openrocket/util/Base64.java @@ -16,6 +16,18 @@ public class Base64 { 'w','x','y','z','0','1','2','3','4','5','6','7','8','9','+','/' }; private static final char PAD = '='; + +// private static final byte[] REVERSE; +// static { +// REVERSE = new byte[128]; +// Arrays.fill(REVERSE, (byte)-1); +// for (int i=0; i<64; i++) { +// REVERSE[ALPHABET[i]] = (byte)i; +// } +// REVERSE['-'] = 62; +// REVERSE['_'] = 63; +// REVERSE[PAD] = 0; +// } private static final Map REVERSE = new HashMap(); static { diff --git a/src/net/sf/openrocket/util/Prefs.java b/src/net/sf/openrocket/util/Prefs.java index dc3e0db40..70a3e558e 100644 --- a/src/net/sf/openrocket/util/Prefs.java +++ b/src/net/sf/openrocket/util/Prefs.java @@ -203,7 +203,7 @@ public class Prefs { public static String getUniqueID() { String id = PREFNODE.get("id", null); if (id == null) { - id = UniqueID.generateHashedID(); + id = UniqueID.uuid(); PREFNODE.put("id", id); } return id; diff --git a/src/net/sf/openrocket/util/TextUtil.java b/src/net/sf/openrocket/util/TextUtil.java index f554dc73b..c6a1bb835 100644 --- a/src/net/sf/openrocket/util/TextUtil.java +++ b/src/net/sf/openrocket/util/TextUtil.java @@ -2,6 +2,28 @@ package net.sf.openrocket.util; public class TextUtil { + private static final char[] HEX = { + '0','1','2','3','4','5','6','7', + '8','9','a','b','c','d','e','f' + }; + + + /** + * Return the bytes formatted as a hexadecimal string. The length of the + * string will be twice the number of bytes, with no spacing between the bytes + * and lowercase letters utilized. + * + * @param bytes the bytes to convert. + * @return the bytes in hexadecimal notation. + */ + public static final String hexString(byte[] bytes) { + StringBuilder sb = new StringBuilder(bytes.length * 2); + for (byte b: bytes) { + sb.append(HEX[(b >>> 4) & 0xF]); + sb.append(HEX[b & 0xF]); + } + return sb.toString(); + } /** * Return a string of the double value with suitable precision (5 digits). diff --git a/src/net/sf/openrocket/util/UniqueID.java b/src/net/sf/openrocket/util/UniqueID.java index e7b56d4ac..1cc082e81 100644 --- a/src/net/sf/openrocket/util/UniqueID.java +++ b/src/net/sf/openrocket/util/UniqueID.java @@ -1,12 +1,8 @@ package net.sf.openrocket.util; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; import java.util.UUID; import java.util.concurrent.atomic.AtomicInteger; -import net.sf.openrocket.gui.main.ExceptionHandler; - public class UniqueID { private static AtomicInteger nextId = new AtomicInteger(1); @@ -34,34 +30,4 @@ public class UniqueID { return UUID.randomUUID().toString(); } - - /** - * Return a hashed unique ID that contains no information whatsoever of the - * originating computer. - * - * @return a unique identifier string that contains no information about the computer. - */ - public static String generateHashedID() { - String id = UUID.randomUUID().toString(); - - try { - MessageDigest algorithm = MessageDigest.getInstance("MD5"); - algorithm.reset(); - algorithm.update(id.getBytes()); - byte[] digest = algorithm.digest(); - - StringBuilder sb = new StringBuilder(); - for (byte b: digest) { - sb.append(String.format("%02X", 0xFF & b)); - } - id = sb.toString(); - - } catch (NoSuchAlgorithmException e) { - ExceptionHandler.handleErrorCondition(e); - id = "" + id.hashCode(); - } - - return id; - } - } diff --git a/src/net/sf/openrocket/utils/MotorPrinter.java b/src/net/sf/openrocket/utils/MotorPrinter.java index 194e15aff..ed6bdb7e2 100644 --- a/src/net/sf/openrocket/utils/MotorPrinter.java +++ b/src/net/sf/openrocket/utils/MotorPrinter.java @@ -37,6 +37,7 @@ public class MotorPrinter { System.out.printf(" Total impulse: %.2f Ns\n", m.getTotalImpulse()); System.out.println(" Diameter: " + m.getDiameter()*1000 + " mm"); System.out.println(" Length: " + m.getLength()*1000 + " mm"); + System.out.println(" Digest: " + m.getDigestString()); if (m instanceof ThrustCurveMotor) { ThrustCurveMotor tc = (ThrustCurveMotor)m; diff --git a/test/net/sf/openrocket/motor/MotorDigestTest.java b/test/net/sf/openrocket/motor/MotorDigestTest.java new file mode 100644 index 000000000..916ab12c4 --- /dev/null +++ b/test/net/sf/openrocket/motor/MotorDigestTest.java @@ -0,0 +1,76 @@ +package net.sf.openrocket.motor; + +import static org.junit.Assert.assertEquals; + +import java.io.UnsupportedEncodingException; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; + +import net.sf.openrocket.motor.MotorDigest.DataType; +import net.sf.openrocket.util.TextUtil; + +import org.junit.Test; + + +public class MotorDigestTest { + + private static final double[] timeArray = { + 0.0, 0.123456789, 0.4115, Math.nextAfter(Math.nextAfter(1.4445, 0), 0) + }; + + private static final double[] massArray = { + 0.54321, 0.43211 + }; + + private static final double[] thrustArray = { + 0.0, 0.2345678, 9999.3335, 0.0 + }; + + private static final int[] intData = { + // Time (ms) + 0, 4, 0, 123, 412, 1445, + // Mass specific (0.1g) + 1, 2, 5432, 4321, + // Thrust (mN) + 5, 4, 0, 235, 9999334, 0 + }; + + + @Test + public void testMotorDigest() throws NoSuchAlgorithmException { + + MessageDigest correct = MessageDigest.getInstance("MD5"); + for (int value: intData) { + correct.update((byte) ((value >>> 24) & 0xFF)); + correct.update((byte) ((value >>> 16) & 0xFF)); + correct.update((byte) ((value >>> 8) & 0xFF)); + correct.update((byte) (value & 0xFF)); + } + + MotorDigest motor = new MotorDigest(); + motor.update(DataType.TIME_ARRAY, timeArray); + motor.update(DataType.MASS_SPECIFIC, massArray); + motor.update(DataType.FORCE_PER_TIME, thrustArray); + + + assertEquals(TextUtil.hexString(correct.digest()), motor.getDigest()); + } + + + @Test + public void testCommentDigest() throws NoSuchAlgorithmException, UnsupportedEncodingException { + + assertEquals(md5("Hello world!"), MotorDigest.digestComment("Hello world! ")); + assertEquals(md5("Hello world!"), MotorDigest.digestComment("\nHello\tworld!\n\r")); + assertEquals(md5("Hello world!"), MotorDigest.digestComment("Hello\r\r\r\nworld!")); + assertEquals(md5("Hello\u00e4 world!"), MotorDigest.digestComment("Hello\u00e4\r\r\nworld!")); + + } + + + private static String md5(String source) + throws NoSuchAlgorithmException, UnsupportedEncodingException { + MessageDigest digest = MessageDigest.getInstance("MD5"); + return TextUtil.hexString(digest.digest(source.getBytes("UTF-8"))); + } +} diff --git a/test/net/sf/openrocket/util/TextUtilTest.java b/test/net/sf/openrocket/util/TextUtilTest.java index 73930cf87..3b7385f00 100644 --- a/test/net/sf/openrocket/util/TextUtilTest.java +++ b/test/net/sf/openrocket/util/TextUtilTest.java @@ -3,9 +3,38 @@ package net.sf.openrocket.util; import static java.lang.Math.PI; import static org.junit.Assert.assertEquals; +import java.util.Random; + import org.junit.Test; public class TextUtilTest { + + @Test + public void textHexString() { + assertEquals("", TextUtil.hexString(new byte[0])); + assertEquals("00", TextUtil.hexString(new byte[] { 0x00 })); + assertEquals("ff", TextUtil.hexString(new byte[] { (byte) 0xff })); + + for (int i=0; i <= 0xff; i++) { + assertEquals(String.format("%02x", i), TextUtil.hexString(new byte[] { (byte) i })); + } + + assertEquals("0f1e2d3c4b5a6978", TextUtil.hexString(new byte[] { + 0x0f, 0x1e, 0x2d, 0x3c, 0x4b, 0x5a, 0x69, 0x78 + })); + + Random rnd = new Random(); + for (int count=0; count<10; count++) { + int n = rnd.nextInt(100); + byte[] bytes = new byte[n]; + rnd.nextBytes(bytes); + StringBuilder sb = new StringBuilder(); + for (byte b: bytes) { + sb.append(String.format("%02x", b & 0xFF)); + } + assertEquals(sb.toString(), TextUtil.hexString(bytes)); + } + } @Test public void specialCaseTest() { diff --git a/test/net/sf/openrocket/util/UniqueIDTest.java b/test/net/sf/openrocket/util/UniqueIDTest.java index eeec61533..c77da324e 100644 --- a/test/net/sf/openrocket/util/UniqueIDTest.java +++ b/test/net/sf/openrocket/util/UniqueIDTest.java @@ -26,25 +26,4 @@ public class UniqueIDTest { assertNotSame(id, UniqueID.uuid()); } - @Test - public void hashedTest() { - String id = UniqueID.generateHashedID(); - assertNotNull(id); - - boolean matchhigh = false; - boolean matchlow = false; - for (int i=0; i<100; i++) { - String newid = UniqueID.generateHashedID(); - assertNotNull(newid); - assertNotSame(id, newid); - assertTrue(newid.matches("^[0-9a-fA-F]{32}$")); - - // Check that both high and low values occur - matchhigh = matchhigh || newid.matches("^([0-9a-fA-F][0-9a-fA-F])*[A-F].*"); - matchlow = matchlow || newid.matches("^([0-9a-fA-F][0-9a-fA-F])*[0-4].*"); - } - assertTrue(matchhigh); - assertTrue(matchlow); - } - }