updates for 0.9.4
authorplaa <plaa@180e2498-e6e9-4542-8430-84ac67f01cd8>
Sun, 4 Oct 2009 15:46:32 +0000 (15:46 +0000)
committerplaa <plaa@180e2498-e6e9-4542-8430-84ac67f01cd8>
Sun, 4 Oct 2009 15:46:32 +0000 (15:46 +0000)
git-svn-id: https://openrocket.svn.sourceforge.net/svnroot/openrocket/trunk@26 180e2498-e6e9-4542-8430-84ac67f01cd8

57 files changed:
.classpath
ChangeLog
TODO
build.properties
build.xml
extra-lib/RXTXcomm.jar [deleted file]
extra-src/altimeter/Alt15K.java [deleted file]
extra-src/altimeter/AltData.java [deleted file]
extra-src/altimeter/RotationLogger.java [deleted file]
extra-src/altimeter/SerialDownload.java [deleted file]
lib-extra/RXTXcomm.jar [new file with mode: 0644]
lib-test/hamcrest-core-1.1.jar [new file with mode: 0644]
lib-test/hamcrest-library-1.1.jar [new file with mode: 0644]
lib-test/jmock-2.5.1.jar [new file with mode: 0644]
lib-test/jmock-junit4-2.5.1.jar [new file with mode: 0644]
lib-test/junit-4.7.jar [new file with mode: 0644]
src-extra/altimeter/Alt15K.java [new file with mode: 0644]
src-extra/altimeter/AltData.java [new file with mode: 0644]
src-extra/altimeter/RotationLogger.java [new file with mode: 0644]
src-extra/altimeter/SerialDownload.java [new file with mode: 0644]
src/net/sf/openrocket/communication/BugReporter.java [new file with mode: 0644]
src/net/sf/openrocket/communication/Communication.java [deleted file]
src/net/sf/openrocket/communication/Communicator.java [new file with mode: 0644]
src/net/sf/openrocket/communication/ConnectionSource.java [new file with mode: 0644]
src/net/sf/openrocket/communication/DefaultConnectionSource.java [new file with mode: 0644]
src/net/sf/openrocket/communication/UpdateInfoRetriever.java [new file with mode: 0644]
src/net/sf/openrocket/database/Databases.java
src/net/sf/openrocket/gui/components/HtmlLabel.java [new file with mode: 0644]
src/net/sf/openrocket/gui/components/ResizeLabel.java [deleted file]
src/net/sf/openrocket/gui/components/StyledLabel.java [new file with mode: 0644]
src/net/sf/openrocket/gui/components/UnitSelector.java
src/net/sf/openrocket/gui/configdialog/FinSetConfig.java
src/net/sf/openrocket/gui/configdialog/FreeformFinSetConfig.java
src/net/sf/openrocket/gui/configdialog/ParachuteConfig.java
src/net/sf/openrocket/gui/configdialog/StreamerConfig.java
src/net/sf/openrocket/gui/dialogs/AboutDialog.java
src/net/sf/openrocket/gui/dialogs/BugReportDialog.java
src/net/sf/openrocket/gui/dialogs/ComponentAnalysisDialog.java
src/net/sf/openrocket/gui/dialogs/CustomMaterialDialog.java
src/net/sf/openrocket/gui/dialogs/LicenseDialog.java
src/net/sf/openrocket/gui/dialogs/MotorChooserDialog.java
src/net/sf/openrocket/gui/dialogs/UpdateInfoDialog.java [new file with mode: 0644]
src/net/sf/openrocket/gui/dialogs/preferences/PreferencesDialog.java
src/net/sf/openrocket/gui/main/BasicFrame.java
src/net/sf/openrocket/gui/main/ComponentAddButtons.java
src/net/sf/openrocket/gui/main/RocketActions.java
src/net/sf/openrocket/gui/main/SimulationPanel.java
src/net/sf/openrocket/gui/plot/PlotPanel.java
src/net/sf/openrocket/util/LimitedInputStream.java [new file with mode: 0644]
src/net/sf/openrocket/util/Prefs.java
src/net/sf/openrocket/utils/MotorCompare.java
test/net/sf/openrocket/communication/BugReportTest.java [new file with mode: 0644]
test/net/sf/openrocket/communication/CommunicationTest.java [deleted file]
test/net/sf/openrocket/communication/ConnectionSourceStub.java [new file with mode: 0644]
test/net/sf/openrocket/communication/HttpURLConnectionMock.java [new file with mode: 0644]
test/net/sf/openrocket/communication/UpdateInfoTest.java [new file with mode: 0644]
test/net/sf/openrocket/rocketcomponent/ComponentCompareTest.java

index fc1bd663e3cfeaf270cd477ca9dd1c58b5bd98be..fdfb5235aed67e9ad07be9409fa568d4b0dff117 100644 (file)
@@ -1,13 +1,17 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <classpath>
        <classpathentry kind="src" path="src"/>
-       <classpathentry kind="src" path="extra-src"/>
+       <classpathentry kind="src" path="src-extra"/>
        <classpathentry kind="src" path="test"/>
        <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
        <classpathentry kind="lib" path="/home/sampo/Projects/OpenRocket/lib/miglayout15-swing.jar"/>
        <classpathentry kind="con" path="org.eclipse.jdt.USER_LIBRARY/JCommon 1.0.16"/>
        <classpathentry kind="con" path="org.eclipse.jdt.USER_LIBRARY/JFreeChart 1.0.13"/>
-       <classpathentry kind="lib" path="/home/sampo/Projects/OpenRocket/extra-lib/RXTXcomm.jar"/>
-       <classpathentry kind="con" path="org.eclipse.jdt.junit.JUNIT_CONTAINER/4"/>
+       <classpathentry kind="lib" path="lib-test/hamcrest-core-1.1.jar"/>
+       <classpathentry kind="lib" path="lib-test/hamcrest-library-1.1.jar"/>
+       <classpathentry kind="lib" path="lib-test/jmock-2.5.1.jar"/>
+       <classpathentry kind="lib" path="lib-test/jmock-junit4-2.5.1.jar"/>
+       <classpathentry kind="lib" path="lib-extra/RXTXcomm.jar"/>
+       <classpathentry kind="lib" path="lib-test/junit-4.7.jar"/>
        <classpathentry kind="output" path="bin"/>
 </classpath>
index 45192f7ba52bc71c8a3e0bfcc161b37c65648397..99ac81b157a70c2369757bc814da762ec12097d5 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,12 @@
+2009-10-04  Sampo Niskanen
+
+       * [BUG] Fixed too high configuration dialogs
+
+2009-10-03  Sampo Niskanen
+
+       * Added debug information to ant build file compilation
+       * Implemented update information fetching (client side)
+
 2009-09-26  Sampo Niskanen
 
        * Implemented custom material editing
diff --git a/TODO b/TODO
index 6909210779a2f1f213a64e86728f4cc3b3b0c37e..9bca110f251a49a1b1da53c35c8cd16dcab86863 100644 (file)
--- a/TODO
+++ b/TODO
@@ -4,7 +4,6 @@ Feature roadmap for OpenRocket 1.0
 
 Must-have:
 
-- Allow editing user-defined materials
 - Go through thrust curves and correct errors
 - Add styrofoam and depron materials
 
@@ -12,12 +11,13 @@ Must-have:
 Bugs:
 
 - Simulation plot dialog forces dialog one button row too high (All/None)
-- All configuration dialogs too high
+- Unit tests fail from ant script
 
 
 Maybe:
 
 - Windows executable wrapper (launch4j)
+- Inform user about software updates
 
 
 Postponed:
@@ -31,18 +31,21 @@ Postponed:
 - Simulate other branches
 - Implement setDefaults() method for RocketComponent
 - BUG: Inner tube cluster rotation, edit with spinner arrows, slider wrong
-- Inform user about software updates
 - Reading thrust curves from external directory
 - NAR/CNES/etc competition validity checking
+- Running from command line
+- Print support
+- Saving as SVG
 
 
 Refactoring tasks:
 
+- Move startup class to src14 directory, remove reflection
 - Remove database etc. initialization from class initialization,
   create separate set of test motors
 - Extract event rules and data saving from Simulator into listeners
 - Change SimulationStatus to include methods for obtaining basic
-  position (maybe even an interface)
+  position (maybe even change to an interface, implements Cloneable)
 - Change Motor (immutable) to be a factory of MotorInstance (stateful)
 
 
@@ -74,4 +77,6 @@ In 0.9.4:
 - Save file as oldest OpenRocket format possible  (for 0.9.4)
 - Non-exception bug handling
 - JTree text is cropped unnecessarily
+- Allow editing user-defined materials
+- [BUG] All configuration dialogs too high
 
index d74b630a03eab1dbc026d4f4bd567148c1635b26..16dafdf9e4bc90c55d2627987223e781b8da1328 100644 (file)
@@ -10,3 +10,7 @@ build.version=0.9.4pre
 
 build.source=default
 
+
+# Whether checking for updates is enabled by default.
+
+build.checkupdates=true
index 97f5a4e41c299b35b95b279870ba79b3288438aa..b77dd04ff00861074af6af99cdf6f43996891a61 100644 (file)
--- a/build.xml
+++ b/build.xml
        
        <path id="test-classpath">
                <path refid="classpath"/>
+               <pathelement location="${basedir}"/>
                <pathelement location="${build-test.dir}"/>
                <pathelement location="${classes.dir}"/>
-               <pathelement location="${ant.library.dir}/junit4.jar"/>
+<!--           <pathelement location="${ant.library.dir}/junit4.jar"/> -->
+               <pathelement location="lib-test/junit-4.7.jar"/>
        </path>
        
 
@@ -51,9 +53,9 @@
        <target name="build">
                <mkdir dir="${classes.dir}"/>
                <echo>Compiling main classes</echo>
-               <javac srcdir="${src.dir}" destdir="${classes.dir}" excludes="${main-dir}/*" classpathref="classpath"/>
+               <javac debug="true" srcdir="${src.dir}" destdir="${classes.dir}" excludes="${main-dir}/*" classpathref="classpath"/>
                <echo>Compiling startup classes</echo>
-               <javac srcdir="${src.dir}/${main-dir}" destdir="${classes.dir}" source="1.4" classpathref="classpath"/>
+               <javac debug="true" srcdir="${src.dir}/${main-dir}" destdir="${classes.dir}" source="1.4" classpathref="classpath"/>
                <copy file="build.properties" todir="${dist.dir}"/>
        </target>
        
@@ -150,7 +152,7 @@ ${criticaltodos}</fail>
        <target name="unittest" description="Execute unit tests" depends="build">
                <echo>Building unit tests</echo>
                <mkdir dir="${build-test.dir}"/>
-               <javac srcdir="${src-test.dir}" destdir="${build-test.dir}" classpathref="test-classpath"/>
+               <javac debug="true" srcdir="${src-test.dir}" destdir="${build-test.dir}" classpathref="test-classpath"/>
                
                <echo>Running unit tests</echo>
                <mkdir dir="tmp/rawtestoutput"/>
diff --git a/extra-lib/RXTXcomm.jar b/extra-lib/RXTXcomm.jar
deleted file mode 100644 (file)
index 84e5f01..0000000
Binary files a/extra-lib/RXTXcomm.jar and /dev/null differ
diff --git a/extra-src/altimeter/Alt15K.java b/extra-src/altimeter/Alt15K.java
deleted file mode 100644 (file)
index 0fba135..0000000
+++ /dev/null
@@ -1,562 +0,0 @@
-package altimeter;
-
-import gnu.io.CommPortIdentifier;
-import gnu.io.PortInUseException;
-import gnu.io.SerialPort;
-import gnu.io.UnsupportedCommOperationException;
-
-import java.io.FileNotFoundException;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.io.PrintStream;
-import java.nio.charset.Charset;
-import java.text.DateFormat;
-import java.util.ArrayList;
-import java.util.Date;
-import java.util.Enumeration;
-import java.util.TimeZone;
-
-/**
- * Class to interface the PerfectFlite Alt15K/WD altimeter.
- * 
- * Also includes a main method that retrieves all flight profiles and saves them to files.
- * 
- * @author Sampo Niskanen <sampo.niskanen@iki.fi>
- */
-
-public class Alt15K {
-       public static final int TIMEOUT = 500;
-       public static final int RWDELAY = 5;
-       
-       private static final boolean DEBUG = false;
-       
-       private static final Charset CHARSET = Charset.forName("ISO-8859-1");
-       
-       private final CommPortIdentifier portID;
-       private SerialPort port = null;
-       private InputStream is = null;
-       private OutputStream os = null;
-       
-       
-
-       @SuppressWarnings("unchecked")
-       public static String[] getNames() {
-               ArrayList<String> list = new ArrayList<String>();;
-               
-               Enumeration pids = CommPortIdentifier.getPortIdentifiers();
-
-               while (pids.hasMoreElements()) {
-                   CommPortIdentifier pid = (CommPortIdentifier) pids.nextElement();
-
-                   if (pid.getPortType() == CommPortIdentifier.PORT_SERIAL)
-                       list.add(pid.getName());
-               }
-               return list.toArray(new String[0]);
-       }
-
-       
-
-       @SuppressWarnings("unchecked")
-       public Alt15K(String name) throws IOException {
-               CommPortIdentifier pID = null;
-               
-               Enumeration portIdentifiers = CommPortIdentifier.getPortIdentifiers();
-               while (portIdentifiers.hasMoreElements()) {
-                   CommPortIdentifier pid = (CommPortIdentifier) portIdentifiers.nextElement();
-                   
-                   if(pid.getPortType() == CommPortIdentifier.PORT_SERIAL &&
-                      pid.getName().equals(name)) {
-                       pID = pid;
-                       break;
-                   }
-               }
-               
-               if (pID==null) {
-                       throw new IOException("Port '"+name+"' not found.");
-               }
-               this.portID = pID;
-       }
-       
-
-       /**
-        * Get altimeter flight data.  The flight profile is chosen by the parameter n,
-        * 0 = latest flight, 1 = second latest, etc.
-        * 
-        * @param n  Which flight profile to use (0=newest, 1=second newest, etc)
-        * @return   The altimeter flight data
-        * @throws IOException                  in case of IOException
-        * @throws PortInUseException   in case of PortInUseException
-        */
-       public AltData getData(int n) throws IOException, PortInUseException {
-               AltData alt = new AltData();
-               ArrayList<Integer> data = new ArrayList<Integer>();
-               byte[] buf;
-               byte[] buf2 = new byte[0];
-               boolean identical = false;  // Whether identical lines have been read
-               
-               if (DEBUG)
-                       System.out.println("  Retrieving altimeter data n="+n);
-               
-               try {
-                       open();
-
-                       // Get version and position data
-                       byte[] ver = getVersionData();
-                       alt.setVersion(new byte[] { ver[0],ver[1] });
-
-                       // Calculate the position requested
-                       if (n > 2)
-                               n = 2;
-                       int position = ver[2] - n;
-                       while (position < 0)
-                               position += 3;
-
-                       if (DEBUG)
-                               System.out.println("  Requesting data from position "+position);
-                       
-                       // Request the data
-                       write("D");
-                       write((byte)position);
-                       write("PS");
-
-                       sleep();
-
-                       // Read preliminary data
-                       buf = read(4);
-                       int msl_level = combine(buf[0],buf[1]);
-                       int datacount = combine(buf[2],buf[3]);
-
-                       if (DEBUG)
-                               System.out.println("  Preliminary data msl="+msl_level+" count="+datacount);
-                       
-                       alt.setMslLevel(msl_level-6000);
-                       alt.setDataSamples(datacount);
-
-                       if (DEBUG)
-                               System.out.println("  Retrieving "+datacount+" samples");
-
-                       long t = System.currentTimeMillis();
-
-                       int count = 0;
-                       while (count < datacount) {
-                               sleep();
-                               write("G");
-                               sleep();
-                               buf = read(17);
-
-                               if (buf.length == 17) {
-                                       // Checksum = sum of all bytes + 1
-                                       // (signedness does not change the result)
-                                       byte checksum = 1;
-                                       for (int i=0; i<16; i++)
-                                               checksum += buf[i];
-                                       if (checksum != buf[16]) {
-                                               printBytes("ERROR: Checksum fail on data (computed="+checksum+
-                                                               " orig="+buf[16]+")",buf);
-                                               System.out.println("Ignoring error");
-                                       }
-                               } else {
-                                       System.err.println("ERROR:  Only "+buf.length+" bytes read, should be 17");
-                               }
-                               
-                               for (int i=0; i<buf.length-1; i+=2) {
-                                       data.add(combine(buf[i],buf[i+1]));
-                                       count++;
-                               }
-                               
-                               /*
-                                * Check whether the data is identical to the previous data batch.  If reading
-                                * too fast, the data seems to become duplicated in the transfer.  We need to check
-                                * whether this has happened by attempting to read more data than is normally
-                                * available.
-                                */
-                               int c, l=Math.min(buf.length, buf2.length);
-                               for (c=0; c<l; c++) {
-                                       if (buf[c] != buf2[c])
-                                               break;
-                               }
-                               if (c==l && buf.length == buf2.length)
-                                       identical = true;
-                               buf2 = buf.clone();
-                       }
-
-                       if (DEBUG)
-                               System.out.println("  Retrieved "+data.size()+" samples in "+
-                                               (System.currentTimeMillis()-t)+" ms");
-
-
-                       // In case of identical lines, check for more data.  This would mean that the
-                       // transfer was corrupted.
-                       if (identical) {
-                               System.err.println("WARNING:  Duplicate data detected, possible error");
-                       }
-
-                       // Test for more data
-                       if (DEBUG)
-                               System.out.println("  Testing for more data");
-                       sleep();
-                       write("G");
-                       sleep();
-                       buf = read(17);
-                       if (buf.length > 0) {
-                               System.err.println("ERROR: Data available after transfer! (length="+buf.length+")");
-                       }
-
-                       
-                       
-                       
-                       
-                       
-                       // Create an int[] array and set it
-                       int[] d = new int[data.size()];
-                       for (int i=0; i<d.length; i++)
-                               d[i] = data.get(i);
-                       alt.setData(d);
-                       
-               //  Catch all exceptions, close the port and re-throw the exception
-               } catch (PortInUseException e) {
-                       close();
-                       throw e;
-               } catch (IOException e) {
-                       close();
-                       throw e;
-               } catch (UnsupportedCommOperationException e) {
-                       close();
-                       throw new RuntimeException("Required function of RxTx library not supported",e);
-               } catch (RuntimeException e) {
-                       // Catch-all for all other types of exceptions
-                       close();
-                       throw e;
-               }
-
-               close();
-               return alt;
-       }
-       
-
-       
-       
-       private byte[] getVersionData() throws PortInUseException, IOException, 
-                                                                                  UnsupportedCommOperationException {
-               byte[] ver = new byte[3];
-               byte[] buf;
-
-               if (DEBUG)
-                       System.out.println("  Retrieving altimeter version information");
-               
-               // Signal to altimeter we are here
-               write((byte)0);
-               sleep(15);  // Sleep for 15ms, data is incoming at 10 samples/sec
-               
-               // Get altimeter version, skip zeros
-               write("PV");
-               sleep();
-               buf = readSkipZero(2);
-               sleep();
-               if (buf.length != 2) {
-                       close();
-                       throw new IOException("Communication with altimeter failed.");
-               }
-               ver[0] = buf[0];
-               ver[1] = buf[1];
-               
-               // Get position of newest data
-               write("M");
-               sleep();
-               buf = read(1);
-               if (buf.length != 1) {
-                       close();
-                       throw new IOException("Communication with altimeter failed.");
-               }
-               ver[2] = buf[0];
-
-               if (DEBUG)
-                       System.out.println("  Received version info "+ver[0]+"."+ver[1]+", position "+ver[2]);
-               
-               return ver;
-       }
-       
-       
-       /**
-        * Delay the communication by a small delay (RWDELAY ms).
-        */
-       private void sleep() {
-               sleep(RWDELAY);
-       }
-       
-       /**
-        * Sleep for the given amount of milliseconds.
-        */
-       private void sleep(int n) {
-               try {
-                       Thread.sleep(n);
-               } catch (InterruptedException ignore) { }
-       }
-       
-       
-       private void open() 
-       throws PortInUseException, IOException, UnsupportedCommOperationException {
-               if (port != null) {
-                       System.err.println("ERROR: open() called with port="+port);
-                       Thread.dumpStack();
-                       close();
-               }
-               
-               if (DEBUG) {
-                       System.out.println("  Opening port...");
-               }
-
-               port = (SerialPort)portID.open("OpenRocket",1000);
-               
-               port.setSerialPortParams(9600, SerialPort.DATABITS_8, SerialPort.STOPBITS_1, 
-                               SerialPort.PARITY_NONE);
-
-               port.setInputBufferSize(1);
-               port.setOutputBufferSize(1);
-
-               port.enableReceiveTimeout(TIMEOUT);
-
-               is = port.getInputStream();
-               os = port.getOutputStream();
-       }
-       
-       
-       private byte[] readSkipZero(int n) throws IOException, UnsupportedCommOperationException {
-               long t = System.currentTimeMillis() + TIMEOUT*2;
-               
-               if (DEBUG) {
-                       System.out.println("    readSkipZero "+n+" bytes");
-               }
-               
-               while (System.currentTimeMillis() < t) {
-                       byte[] buf = read(n);
-                       if (DEBUG)
-                               printBytes("      Received",buf);
-                       
-                       if (buf.length == 0)  // No data available
-                               return buf;
-                       
-                       // Skip zeros
-                       int i;
-                       for (i=0; i<buf.length; i++)
-                               if (buf[i] != 0)
-                                       break;
-                       
-                       if (i==0)   // No zeros to skip
-                               return buf;
-                       
-                       if (i < buf.length) {
-                               // Partially read
-                               int count = buf.length-i;  // No. of data bytes
-                               byte[] array = new byte[n];
-                               System.arraycopy(buf, i, array, 0, count);
-                               buf = read(n-count);
-                               if (DEBUG)
-                                       printBytes("      Received (partial)",buf);
-                               System.arraycopy(buf, 0, array, count, buf.length);
-                               
-                               if (DEBUG)
-                                       printBytes("    Returning",array);
-                               return array;
-                       }
-               }
-               
-               if (DEBUG)
-                       System.out.println("  No data read, returning empty");
-               return new byte[0];  // no data, only zeros
-       }
-       
-
-       private byte[] read(int n) throws IOException, UnsupportedCommOperationException {
-               byte[] bytes = new byte[n];
-               
-               port.enableReceiveThreshold(n);
-               
-               long t = System.currentTimeMillis() + TIMEOUT;
-               int count = 0;
-
-               if (DEBUG)
-                       System.out.println("    Reading "+n+" bytes");
-
-               while (count < n && System.currentTimeMillis() < t) {
-                       byte[] buf = new byte[n-count];
-                       int c = is.read(buf);
-                       System.arraycopy(buf, 0, bytes, count, c);
-                       count += c;
-               }
-               
-               byte[] array = new byte[count];
-               System.arraycopy(bytes, 0, array, 0, count);
-               
-               if (DEBUG)
-                       printBytes("    Returning",array);
-               
-               return array;
-       }
-       
-       private void write(String s) throws IOException {
-               write(s.getBytes(CHARSET));
-       }
-       
-       private void write(byte ... bytes) throws IOException {
-               if (DEBUG)
-                       printBytes("    Writing",bytes);
-               os.write(bytes);
-       }
-       
-       private void close() {
-               if (DEBUG)
-                       System.out.println("  Closing port");
-               
-               SerialPort p = port;
-               port = null;
-               is = null;
-               os = null;
-               if (p != null)
-                       p.close();
-       }
-       
-       
-       
-
-       
-       public static void main(String[] arg) {
-               
-               if (arg.length != 1) {
-                       System.err.println("Usage:  java Alt15K <basename>");
-                       System.err.println("Files will be saved <basename>-old.log, -med and -new");
-                       return;
-               }
-               
-               
-               String device = null;
-               String[] devices = Alt15K.getNames();
-               for (int i=0; i<devices.length; i++) {
-                       if (devices[i].matches(".*USB.*")) {
-                               device = devices[i];
-                               break;
-                       }
-               }
-               if (device == null) {
-                       System.out.println("Device not found.");
-                       return;
-               }
-               
-               
-               System.out.println("Selected device "+device);
-               
-               AltData alt = null;
-               String file;
-               try {
-                       Alt15K p = new Alt15K(device);
-
-                       System.out.println("Retrieving newest data...");
-                       alt = p.getData(0);
-                       System.out.println("Apogee at "+alt.getApogee()+" feet");
-
-                       file = arg[0]+"-new.log";
-                       System.out.println("Saving data to "+file+"...");
-                       savefile(file,alt);
-                       
-                       
-                       System.out.println("Retrieving medium data...");
-                       alt = p.getData(1);
-                       System.out.println("Apogee at "+alt.getApogee()+" feet");
-
-                       file = arg[0]+"-med.log";
-                       System.out.println("Saving data to "+file+"...");
-                       savefile(file,alt);
-                       
-                       
-                       System.out.println("Retrieving oldest data...");
-                       alt = p.getData(2);
-                       System.out.println("Apogee at "+alt.getApogee()+" feet");
-
-                       file = arg[0]+"-old.log";
-                       System.out.println("Saving data to "+file+"...");
-                       savefile(file,alt);
-                       
-               } catch (IOException e) {
-                       e.printStackTrace();
-               } catch (PortInUseException e) {
-                       e.printStackTrace();
-               }
-
-//             System.out.println(alt);
-//             alt.printData();
-               
-       }
-       
-       
-       static private void savefile(String file, AltData data) throws FileNotFoundException {
-               
-               PrintStream output = new PrintStream(file);
-               
-               // WTF is this so difficult?!?
-               DateFormat fmt = DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.MEDIUM);
-               TimeZone tz=TimeZone.getTimeZone("GMT+3");
-               fmt.setTimeZone(tz);
-
-               output.println("# Alt15K data, file "+file);
-               output.println("# Data retrieved at: "+fmt.format(new Date()));
-               output.println("# Values are in feet above launch level");
-               output.println("# ");
-               output.println("# Apogee = "+data.getApogee());
-               output.println("# MSL level = "+data.getMslLevel());
-               output.println("# Data count = "+data.getDataSamples());
-               
-               byte[] b = data.getVersion();
-               String s="";
-               for (int i=0; i<b.length; i++) {
-                       if (s.equals(""))
-                               s = ""+((int)b[i]);
-                       else 
-                               s = s+"."+((int)b[i]);
-               }
-               output.println("# Altimeter version = " + s);
-               
-               int[] values = data.getData();
-               for (int i=0; i < values.length; i++) {
-                       output.println(""+values[i]);
-               }
-               
-               output.close();
-       }
-       
-       
-       static private void printBytes(String str, byte[] b) {
-               printBytes(str, b,b.length);
-       }
-       
-       static private void printBytes(String str, byte[] b, int n) {
-               String s;
-               s = str+" "+n+" bytes:";
-               for (int i=0; i<n; i++) {
-                       s += " "+unsign(b[i]);
-               }
-               System.out.println(s);
-       }
-       
-       static private int unsign(byte b) {
-               if (b >= 0)
-                       return b;
-               else
-                       return 256 + b;
-       }
-       
-       @SuppressWarnings("unused")
-       static private int combine(int a, int b) {
-               return 256*a + b;
-       }
-       
-       static private int combine(byte a, byte b) {
-               int val = 256*unsign(a)+unsign(b);
-               if (val <= 32767)
-                       return val;
-               else
-                       return val-65536;
-                       
-       }
-       
-}
diff --git a/extra-src/altimeter/AltData.java b/extra-src/altimeter/AltData.java
deleted file mode 100644 (file)
index 63314c7..0000000
+++ /dev/null
@@ -1,81 +0,0 @@
-package altimeter;
-
-public class AltData {
-
-       private int mslLevel = 0;
-       private int samples = 0;
-       private int[] data = null;
-       private byte[] version = null;
-       
-       
-       public void setMslLevel(int msl) {
-               mslLevel = msl;
-       }
-       public int getMslLevel() {
-               return mslLevel;
-       }
-       
-       public void setDataSamples(int s) {
-               samples = s;
-       }
-       public int getDataSamples() {
-               return samples;
-       }
-       
-       public void setVersion(byte[] v) {
-               if (v==null)
-                       version = null;
-               else 
-                       version = v.clone();
-       }
-       public byte[] getVersion() {
-               if (version == null)
-                       return null;
-               return version.clone();         
-       }
-       
-       public void setData(int[] data) {
-               if (data==null)
-                       this.data = null;
-               else 
-                       this.data = data.clone();
-       }
-       public int[] getData() {
-               if (data == null)
-                       return null;
-               return data.clone();
-       }
-
-       public int getApogee() {
-               if (data == null || data.length==0)
-                       return 0;
-               int max = Integer.MIN_VALUE;
-               for (int i=0; i<data.length; i++) {
-                       if (data[i] > max)
-                               max = data[i];
-               }
-               return max;
-       }
-       
-       @Override
-       public String toString() {
-               String s = "AltData(";
-               s += "MSL:"+getMslLevel()+",";
-               s += "Apogee:"+getApogee()+",";
-               s += "Samples:"+getDataSamples();
-               s += ")";
-               return s;
-       }
-       
-       public void printData() {
-               System.out.println(toString()+":");
-               for (int i=0; i<data.length; i+=8) {
-                       String s = "  "+i+":";
-                       for (int j=0; j<8 && (i+j)<data.length; j++) {
-                               s += " "+data[i+j];
-                       }
-                       System.out.println(s);
-               }
-       }
-       
-}
diff --git a/extra-src/altimeter/RotationLogger.java b/extra-src/altimeter/RotationLogger.java
deleted file mode 100644 (file)
index 45e7d76..0000000
+++ /dev/null
@@ -1,356 +0,0 @@
-package altimeter;
-
-import gnu.io.CommPortIdentifier;
-import gnu.io.PortInUseException;
-import gnu.io.SerialPort;
-import gnu.io.UnsupportedCommOperationException;
-
-import java.io.FileInputStream;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.util.ArrayList;
-import java.util.Enumeration;
-
-/**
- * Class to interface the PerfectFlite Alt15K/WD altimeter.
- * 
- * Also includes a main method that retrieves all flight profiles and saves them to files.
- * 
- * @author Sampo Niskanen <sampo.niskanen@iki.fi>
- */
-
-public class RotationLogger {
-       private static final boolean DEBUG = false;
-       
-       private static final int BYTES = 65536; 
-       
-       
-       private final CommPortIdentifier portID;
-       private SerialPort port = null;
-       private InputStream is = null;
-       private OutputStream os = null;
-       
-       
-
-       @SuppressWarnings("unchecked")
-       public static String[] getNames() {
-               ArrayList<String> list = new ArrayList<String>();;
-               
-               Enumeration pids = CommPortIdentifier.getPortIdentifiers();
-
-               while (pids.hasMoreElements()) {
-                   CommPortIdentifier pid = (CommPortIdentifier) pids.nextElement();
-
-                   if (pid.getPortType() == CommPortIdentifier.PORT_SERIAL)
-                       list.add(pid.getName());
-               }
-               return list.toArray(new String[0]);
-       }
-
-       
-       
-       
-
-       @SuppressWarnings("unchecked")
-       public RotationLogger(String name) throws IOException {
-               CommPortIdentifier portID = null;
-               
-               Enumeration portIdentifiers = CommPortIdentifier.getPortIdentifiers();
-               while (portIdentifiers.hasMoreElements()) {
-                   CommPortIdentifier pid = (CommPortIdentifier) portIdentifiers.nextElement();
-                   
-                   if(pid.getPortType() == CommPortIdentifier.PORT_SERIAL &&
-                      pid.getName().equals(name)) {
-                       portID = pid;
-                       break;
-                   }
-               }
-               
-               if (portID==null) {
-                       throw new IOException("Port '"+name+"' not found.");
-               }
-               this.portID = portID;
-       }
-       
-       
-       
-       
-       
-       
-       public void readData() throws IOException, PortInUseException {
-               int c;
-               
-               int[] data = new int[BYTES];
-               
-               FileOutputStream rawdump = null;
-               
-               
-               try {
-                       open();
-
-                       System.err.println("Sending dump mode command...");
-                       
-                       for (int i=0; i<16; i++) {
-                               os.write('D');
-                               try {
-                                       Thread.sleep(10);
-                               } catch (InterruptedException ignore) { }
-                       }
-                       
-                       System.err.println("Waiting for response...");
-                       while (true) {
-                               c = is.read();
-                               if (c == 'K') {
-                                       break;
-                               } else {
-                                       System.err.printf("Received spurious c=%d\n",c);
-                               }
-                       }
-                       
-                       System.err.println("Received response.");
-                       
-
-                       
-                       System.err.println("Opening 'rawdump'...");
-                       rawdump = new FileOutputStream("rawdump");
-                       
-                       
-                       
-                       System.err.println("Performing dump...");
-
-                       os.write('A');
-
-                       byte[] buffer = new byte[1024];
-                       int printCount = 0;
-                       for (int count=0; count < BYTES; ) {
-                               if ((BYTES-count) < buffer.length) {
-                                       buffer = new byte[BYTES-count];
-                               }
-                               
-                               int n = is.read(buffer);
-                               if (n < 0) {
-                                       System.err.println("Error condition, n="+n);
-                                       return;
-                               }
-                       
-                               rawdump.write(buffer, 0, n);
-                               
-                               for (int i=0; i<n; i++) {
-                                       data[count+i] = unsign(buffer[i]);
-                               }
-                               count += n;
-                               if (count - printCount > 1024) {
-                                       System.err.println("Read "+count+" bytes...");
-                                       printCount = count;
-                               }
-                       }
-
-
-                       System.err.println("Verifying checksum...");
-                       int reported = is.read();
-                       
-                       byte computed = 0;
-                       for (int i=0; i < data.length; i++) {
-                               computed += data[i];
-                       }
-                       if (computed == reported) {
-                               System.err.println("Checksum ok ("+computed+")");
-                       } else {
-                               System.err.println("Error in checksum, computed="+computed+
-                                               " reported="+reported);
-                       }
-                       
-                       System.err.println("Communication done.");
-                       
-               } catch (UnsupportedCommOperationException e) {
-                       // TODO Auto-generated catch block
-                       e.printStackTrace();
-               } finally {
-                       close();
-                       if (rawdump != null)
-                               rawdump.close();
-               }
-               
-               convertData(data);
-               
-       }
-       
-       
-       
-       ////////////  Data interpretation   //////////////      
-       
-       
-       private static void convertData(int[] data) {
-
-               System.err.println("Converting data...");
-
-               int lastBuffer = data[0xffff];
-               if (lastBuffer < 0 || lastBuffer > 3) {
-                       System.err.println("Illegal last accessed buffer: "+lastBuffer);
-                       return;
-               }
-               System.err.println("Last used buffer: "+lastBuffer);
-               
-               for (int i=4; i>0; i--) {
-                       int n = (lastBuffer + i) % 4;
-                       int bufNumber = 4-i;
-                       
-                       convertBuffer(data, n * (BYTES/4), bufNumber);
-               }
-               
-       }
-       
-       
-       private static void convertBuffer(int[] data, int position, int bufNumber) {
-               int startPosition;
-               
-               startPosition = data[position + 0xfd] << 8 + data[position+0xfe];
-
-               // 50 samples per 128 bytes 
-               int startTime = (startPosition -position) * 50 / 128;
-               
-               System.err.println("  Buffer "+ bufNumber + " (at position "+position+")...");
-               System.err.println("  Start position "+startPosition+" time "+startTime);
-
-               System.out.println("# Buffer "+bufNumber);
-               System.out.println("# Start position t="+startTime);
-               
-               
-               int t = 0;
-               for (int page = 0; page < 128; page++) {
-                       int pageStart = position + page * 128;
-
-                       if (pageStart == startPosition) {
-                               System.out.println("# ---clip---");
-                       }
-
-                       for (int i=0; i<125; i += 5) {
-                               int sample1, sample2;
-                               
-                               int start = pageStart + i;
-//                             System.err.println("page="+page+" i="+i+
-//                                             " position="+position+" pageStart="+pageStart+" start="+start);
-                               
-                               sample1 = (data[start] << 2) + (data[start+1] >> 6);
-                               sample2 = ((data[start+1] & 0x3f) << 4) + (data[start+2] >> 4);
-                               System.out.printf("%d  %4d  %4d %4d\n", bufNumber, t, sample1, sample2);
-                               t++;
-                               
-                               sample1 = ((data[start+2] & 0x0f) << 6) + (data[start+3] >> 2);
-                               sample2 = ((data[start+3] & 3) << 8) + data[start+4];
-                               System.out.printf("%d  %4d  %4d %4d\n", bufNumber, t, sample1, sample2);
-                               t++;
-                       }
-               }
-       }
-
-       
-       
-       private void open() throws PortInUseException, IOException, 
-                       UnsupportedCommOperationException {
-               
-               if (port != null) {
-                       System.err.println("ERROR: open() called with port="+port);
-                       Thread.dumpStack();
-                       close();
-               }
-               
-               if (DEBUG) {
-                       System.err.println("  Opening port...");
-               }
-
-               port = (SerialPort)portID.open("OpenRocket",1000);
-               
-               port.setSerialPortParams(9600, SerialPort.DATABITS_8, SerialPort.STOPBITS_1, 
-                               SerialPort.PARITY_NONE);
-
-               port.setInputBufferSize(1);
-               port.setOutputBufferSize(1);
-
-               is = port.getInputStream();
-               os = port.getOutputStream();
-       }
-       
-       
-       private void close() {
-               if (DEBUG)
-                       System.err.println("  Closing port");
-               
-               SerialPort p = port;
-               port = null;
-               is = null;
-               if (p != null)
-                       p.close();
-       }
-       
-       
-       
-       private static int unsign(byte b) {
-               if (b >= 0)
-                       return b;
-               else
-               return 256 + b;
-       }
-       
-       
-
-       
-       public static void main(String[] arg) throws Exception {
-               
-               if (arg.length > 2) {
-                       System.err.println("Illegal arguments.");
-                       return;
-               }
-               if (arg.length == 1) {
-                       FileInputStream is = new FileInputStream(arg[0]);
-                       byte[] buffer = new byte[BYTES];
-                       int n = is.read(buffer);
-                       if (n != BYTES) {
-                               System.err.println("Could read only "+n+" bytes");
-                               return;
-                       }
-                       
-                       int[] data = new int[BYTES];
-                       for (int i=0; i<BYTES; i++) {
-                               data[i] = unsign(buffer[i]);
-                       }
-
-                       int checksum=0;
-                       for (int i=0; i<BYTES; i++) {
-                               checksum += data[i];
-                       }
-                       checksum = checksum%256;
-                       System.err.println("Checksum: "+checksum);
-                       
-                       convertData(data);
-                       return;                 
-               }
-               
-               
-               String device = null;
-               String[] devices = RotationLogger.getNames();
-               for (int i=0; i<devices.length; i++) {
-                       if (devices[i].matches(".*USB.*")) {
-                               device = devices[i];
-                               break;
-                       }
-               }
-               if (device == null) {
-                       System.err.println("Device not found.");
-                       return;
-               }
-               
-               
-               System.err.println("Selected device "+device);
-               
-               
-               RotationLogger p = new RotationLogger(device);
-               
-               p.readData();
-               
-       }
-       
-       
-}
diff --git a/extra-src/altimeter/SerialDownload.java b/extra-src/altimeter/SerialDownload.java
deleted file mode 100644 (file)
index d82e582..0000000
+++ /dev/null
@@ -1,184 +0,0 @@
-package altimeter;
-
-import gnu.io.CommPortIdentifier;
-import gnu.io.PortInUseException;
-import gnu.io.SerialPort;
-import gnu.io.UnsupportedCommOperationException;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.util.ArrayList;
-import java.util.Enumeration;
-
-/**
- * Class to interface the PerfectFlite Alt15K/WD altimeter.
- * 
- * Also includes a main method that retrieves all flight profiles and saves them to files.
- * 
- * @author Sampo Niskanen <sampo.niskanen@iki.fi>
- */
-
-public class SerialDownload {
-       private static final boolean DEBUG = false;
-       
-       private static final int MAGIC = 666;
-       
-       private final CommPortIdentifier portID;
-       private SerialPort port = null;
-       private InputStream is = null;
-       
-       
-
-       @SuppressWarnings("unchecked")
-       public static String[] getNames() {
-               ArrayList<String> list = new ArrayList<String>();;
-               
-               Enumeration pids = CommPortIdentifier.getPortIdentifiers();
-
-               while (pids.hasMoreElements()) {
-                   CommPortIdentifier pid = (CommPortIdentifier) pids.nextElement();
-
-                   if (pid.getPortType() == CommPortIdentifier.PORT_SERIAL)
-                       list.add(pid.getName());
-               }
-               return list.toArray(new String[0]);
-       }
-
-       
-       
-       
-
-       @SuppressWarnings("unchecked")
-       public SerialDownload(String name) throws IOException {
-               CommPortIdentifier portID = null;
-               
-               Enumeration portIdentifiers = CommPortIdentifier.getPortIdentifiers();
-               while (portIdentifiers.hasMoreElements()) {
-                   CommPortIdentifier pid = (CommPortIdentifier) portIdentifiers.nextElement();
-                   
-                   if(pid.getPortType() == CommPortIdentifier.PORT_SERIAL &&
-                      pid.getName().equals(name)) {
-                       portID = pid;
-                       break;
-                   }
-               }
-               
-               if (portID==null) {
-                       throw new IOException("Port '"+name+"' not found.");
-               }
-               this.portID = portID;
-       }
-       
-       
-       
-       
-       
-       
-       public void readData() throws IOException, PortInUseException {
-               long t0 = -1;
-               long t;
-               
-               int previous = MAGIC;
-               
-               
-               try {
-                       open();
-                       
-                       System.err.println("Ready to read...");
-                       while (true) {
-                               int c = is.read();
-                               t = System.nanoTime();
-                               if (t0 < 0)
-                                       t0 = t;
-                               
-                               System.out.printf("%10.6f %d\n", ((double)t-t0)/1000000000.0, c);
-                               
-                               if (previous == MAGIC) {
-                                       previous = c;
-                               } else {
-                                       System.out.printf("# Altitude: %5d\n", previous*256 + c);
-                                       previous = MAGIC;
-                               }
-                               
-                               if (c < 0)
-                                       break;
-                       }
-                       
-                       
-               } catch (UnsupportedCommOperationException e) {
-                       // TODO Auto-generated catch block
-                       e.printStackTrace();
-               } finally {
-                       close();
-               }
-       }
-
-       
-       
-       private void open() throws PortInUseException, IOException, 
-                       UnsupportedCommOperationException {
-               
-               if (port != null) {
-                       System.err.println("ERROR: open() called with port="+port);
-                       Thread.dumpStack();
-                       close();
-               }
-               
-               if (DEBUG) {
-                       System.err.println("  Opening port...");
-               }
-
-               port = (SerialPort)portID.open("OpenRocket",1000);
-               
-               port.setSerialPortParams(9600, SerialPort.DATABITS_8, SerialPort.STOPBITS_1, 
-                               SerialPort.PARITY_NONE);
-
-               port.setInputBufferSize(1);
-               port.setOutputBufferSize(1);
-
-               is = port.getInputStream();
-       }
-       
-       
-       private void close() {
-               if (DEBUG)
-                       System.err.println("  Closing port");
-               
-               SerialPort p = port;
-               port = null;
-               is = null;
-               if (p != null)
-                       p.close();
-       }
-       
-       
-       
-
-       
-       public static void main(String[] arg) throws Exception {
-               
-               String device = null;
-               String[] devices = SerialDownload.getNames();
-               for (int i=0; i<devices.length; i++) {
-                       if (devices[i].matches(".*USB.*")) {
-                               device = devices[i];
-                               break;
-                       }
-               }
-               if (device == null) {
-                       System.err.println("Device not found.");
-                       return;
-               }
-               
-               
-               System.err.println("Selected device "+device);
-               
-               
-               SerialDownload p = new SerialDownload(device);
-               
-               p.readData();
-               
-       }
-       
-       
-}
diff --git a/lib-extra/RXTXcomm.jar b/lib-extra/RXTXcomm.jar
new file mode 100644 (file)
index 0000000..84e5f01
Binary files /dev/null and b/lib-extra/RXTXcomm.jar differ
diff --git a/lib-test/hamcrest-core-1.1.jar b/lib-test/hamcrest-core-1.1.jar
new file mode 100644 (file)
index 0000000..5f1d5ce
Binary files /dev/null and b/lib-test/hamcrest-core-1.1.jar differ
diff --git a/lib-test/hamcrest-library-1.1.jar b/lib-test/hamcrest-library-1.1.jar
new file mode 100644 (file)
index 0000000..40610c9
Binary files /dev/null and b/lib-test/hamcrest-library-1.1.jar differ
diff --git a/lib-test/jmock-2.5.1.jar b/lib-test/jmock-2.5.1.jar
new file mode 100644 (file)
index 0000000..4415dfb
Binary files /dev/null and b/lib-test/jmock-2.5.1.jar differ
diff --git a/lib-test/jmock-junit4-2.5.1.jar b/lib-test/jmock-junit4-2.5.1.jar
new file mode 100644 (file)
index 0000000..fb3697a
Binary files /dev/null and b/lib-test/jmock-junit4-2.5.1.jar differ
diff --git a/lib-test/junit-4.7.jar b/lib-test/junit-4.7.jar
new file mode 100644 (file)
index 0000000..700ad69
Binary files /dev/null and b/lib-test/junit-4.7.jar differ
diff --git a/src-extra/altimeter/Alt15K.java b/src-extra/altimeter/Alt15K.java
new file mode 100644 (file)
index 0000000..0fba135
--- /dev/null
@@ -0,0 +1,562 @@
+package altimeter;
+
+import gnu.io.CommPortIdentifier;
+import gnu.io.PortInUseException;
+import gnu.io.SerialPort;
+import gnu.io.UnsupportedCommOperationException;
+
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.PrintStream;
+import java.nio.charset.Charset;
+import java.text.DateFormat;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.Enumeration;
+import java.util.TimeZone;
+
+/**
+ * Class to interface the PerfectFlite Alt15K/WD altimeter.
+ * 
+ * Also includes a main method that retrieves all flight profiles and saves them to files.
+ * 
+ * @author Sampo Niskanen <sampo.niskanen@iki.fi>
+ */
+
+public class Alt15K {
+       public static final int TIMEOUT = 500;
+       public static final int RWDELAY = 5;
+       
+       private static final boolean DEBUG = false;
+       
+       private static final Charset CHARSET = Charset.forName("ISO-8859-1");
+       
+       private final CommPortIdentifier portID;
+       private SerialPort port = null;
+       private InputStream is = null;
+       private OutputStream os = null;
+       
+       
+
+       @SuppressWarnings("unchecked")
+       public static String[] getNames() {
+               ArrayList<String> list = new ArrayList<String>();;
+               
+               Enumeration pids = CommPortIdentifier.getPortIdentifiers();
+
+               while (pids.hasMoreElements()) {
+                   CommPortIdentifier pid = (CommPortIdentifier) pids.nextElement();
+
+                   if (pid.getPortType() == CommPortIdentifier.PORT_SERIAL)
+                       list.add(pid.getName());
+               }
+               return list.toArray(new String[0]);
+       }
+
+       
+
+       @SuppressWarnings("unchecked")
+       public Alt15K(String name) throws IOException {
+               CommPortIdentifier pID = null;
+               
+               Enumeration portIdentifiers = CommPortIdentifier.getPortIdentifiers();
+               while (portIdentifiers.hasMoreElements()) {
+                   CommPortIdentifier pid = (CommPortIdentifier) portIdentifiers.nextElement();
+                   
+                   if(pid.getPortType() == CommPortIdentifier.PORT_SERIAL &&
+                      pid.getName().equals(name)) {
+                       pID = pid;
+                       break;
+                   }
+               }
+               
+               if (pID==null) {
+                       throw new IOException("Port '"+name+"' not found.");
+               }
+               this.portID = pID;
+       }
+       
+
+       /**
+        * Get altimeter flight data.  The flight profile is chosen by the parameter n,
+        * 0 = latest flight, 1 = second latest, etc.
+        * 
+        * @param n  Which flight profile to use (0=newest, 1=second newest, etc)
+        * @return   The altimeter flight data
+        * @throws IOException                  in case of IOException
+        * @throws PortInUseException   in case of PortInUseException
+        */
+       public AltData getData(int n) throws IOException, PortInUseException {
+               AltData alt = new AltData();
+               ArrayList<Integer> data = new ArrayList<Integer>();
+               byte[] buf;
+               byte[] buf2 = new byte[0];
+               boolean identical = false;  // Whether identical lines have been read
+               
+               if (DEBUG)
+                       System.out.println("  Retrieving altimeter data n="+n);
+               
+               try {
+                       open();
+
+                       // Get version and position data
+                       byte[] ver = getVersionData();
+                       alt.setVersion(new byte[] { ver[0],ver[1] });
+
+                       // Calculate the position requested
+                       if (n > 2)
+                               n = 2;
+                       int position = ver[2] - n;
+                       while (position < 0)
+                               position += 3;
+
+                       if (DEBUG)
+                               System.out.println("  Requesting data from position "+position);
+                       
+                       // Request the data
+                       write("D");
+                       write((byte)position);
+                       write("PS");
+
+                       sleep();
+
+                       // Read preliminary data
+                       buf = read(4);
+                       int msl_level = combine(buf[0],buf[1]);
+                       int datacount = combine(buf[2],buf[3]);
+
+                       if (DEBUG)
+                               System.out.println("  Preliminary data msl="+msl_level+" count="+datacount);
+                       
+                       alt.setMslLevel(msl_level-6000);
+                       alt.setDataSamples(datacount);
+
+                       if (DEBUG)
+                               System.out.println("  Retrieving "+datacount+" samples");
+
+                       long t = System.currentTimeMillis();
+
+                       int count = 0;
+                       while (count < datacount) {
+                               sleep();
+                               write("G");
+                               sleep();
+                               buf = read(17);
+
+                               if (buf.length == 17) {
+                                       // Checksum = sum of all bytes + 1
+                                       // (signedness does not change the result)
+                                       byte checksum = 1;
+                                       for (int i=0; i<16; i++)
+                                               checksum += buf[i];
+                                       if (checksum != buf[16]) {
+                                               printBytes("ERROR: Checksum fail on data (computed="+checksum+
+                                                               " orig="+buf[16]+")",buf);
+                                               System.out.println("Ignoring error");
+                                       }
+                               } else {
+                                       System.err.println("ERROR:  Only "+buf.length+" bytes read, should be 17");
+                               }
+                               
+                               for (int i=0; i<buf.length-1; i+=2) {
+                                       data.add(combine(buf[i],buf[i+1]));
+                                       count++;
+                               }
+                               
+                               /*
+                                * Check whether the data is identical to the previous data batch.  If reading
+                                * too fast, the data seems to become duplicated in the transfer.  We need to check
+                                * whether this has happened by attempting to read more data than is normally
+                                * available.
+                                */
+                               int c, l=Math.min(buf.length, buf2.length);
+                               for (c=0; c<l; c++) {
+                                       if (buf[c] != buf2[c])
+                                               break;
+                               }
+                               if (c==l && buf.length == buf2.length)
+                                       identical = true;
+                               buf2 = buf.clone();
+                       }
+
+                       if (DEBUG)
+                               System.out.println("  Retrieved "+data.size()+" samples in "+
+                                               (System.currentTimeMillis()-t)+" ms");
+
+
+                       // In case of identical lines, check for more data.  This would mean that the
+                       // transfer was corrupted.
+                       if (identical) {
+                               System.err.println("WARNING:  Duplicate data detected, possible error");
+                       }
+
+                       // Test for more data
+                       if (DEBUG)
+                               System.out.println("  Testing for more data");
+                       sleep();
+                       write("G");
+                       sleep();
+                       buf = read(17);
+                       if (buf.length > 0) {
+                               System.err.println("ERROR: Data available after transfer! (length="+buf.length+")");
+                       }
+
+                       
+                       
+                       
+                       
+                       
+                       // Create an int[] array and set it
+                       int[] d = new int[data.size()];
+                       for (int i=0; i<d.length; i++)
+                               d[i] = data.get(i);
+                       alt.setData(d);
+                       
+               //  Catch all exceptions, close the port and re-throw the exception
+               } catch (PortInUseException e) {
+                       close();
+                       throw e;
+               } catch (IOException e) {
+                       close();
+                       throw e;
+               } catch (UnsupportedCommOperationException e) {
+                       close();
+                       throw new RuntimeException("Required function of RxTx library not supported",e);
+               } catch (RuntimeException e) {
+                       // Catch-all for all other types of exceptions
+                       close();
+                       throw e;
+               }
+
+               close();
+               return alt;
+       }
+       
+
+       
+       
+       private byte[] getVersionData() throws PortInUseException, IOException, 
+                                                                                  UnsupportedCommOperationException {
+               byte[] ver = new byte[3];
+               byte[] buf;
+
+               if (DEBUG)
+                       System.out.println("  Retrieving altimeter version information");
+               
+               // Signal to altimeter we are here
+               write((byte)0);
+               sleep(15);  // Sleep for 15ms, data is incoming at 10 samples/sec
+               
+               // Get altimeter version, skip zeros
+               write("PV");
+               sleep();
+               buf = readSkipZero(2);
+               sleep();
+               if (buf.length != 2) {
+                       close();
+                       throw new IOException("Communication with altimeter failed.");
+               }
+               ver[0] = buf[0];
+               ver[1] = buf[1];
+               
+               // Get position of newest data
+               write("M");
+               sleep();
+               buf = read(1);
+               if (buf.length != 1) {
+                       close();
+                       throw new IOException("Communication with altimeter failed.");
+               }
+               ver[2] = buf[0];
+
+               if (DEBUG)
+                       System.out.println("  Received version info "+ver[0]+"."+ver[1]+", position "+ver[2]);
+               
+               return ver;
+       }
+       
+       
+       /**
+        * Delay the communication by a small delay (RWDELAY ms).
+        */
+       private void sleep() {
+               sleep(RWDELAY);
+       }
+       
+       /**
+        * Sleep for the given amount of milliseconds.
+        */
+       private void sleep(int n) {
+               try {
+                       Thread.sleep(n);
+               } catch (InterruptedException ignore) { }
+       }
+       
+       
+       private void open() 
+       throws PortInUseException, IOException, UnsupportedCommOperationException {
+               if (port != null) {
+                       System.err.println("ERROR: open() called with port="+port);
+                       Thread.dumpStack();
+                       close();
+               }
+               
+               if (DEBUG) {
+                       System.out.println("  Opening port...");
+               }
+
+               port = (SerialPort)portID.open("OpenRocket",1000);
+               
+               port.setSerialPortParams(9600, SerialPort.DATABITS_8, SerialPort.STOPBITS_1, 
+                               SerialPort.PARITY_NONE);
+
+               port.setInputBufferSize(1);
+               port.setOutputBufferSize(1);
+
+               port.enableReceiveTimeout(TIMEOUT);
+
+               is = port.getInputStream();
+               os = port.getOutputStream();
+       }
+       
+       
+       private byte[] readSkipZero(int n) throws IOException, UnsupportedCommOperationException {
+               long t = System.currentTimeMillis() + TIMEOUT*2;
+               
+               if (DEBUG) {
+                       System.out.println("    readSkipZero "+n+" bytes");
+               }
+               
+               while (System.currentTimeMillis() < t) {
+                       byte[] buf = read(n);
+                       if (DEBUG)
+                               printBytes("      Received",buf);
+                       
+                       if (buf.length == 0)  // No data available
+                               return buf;
+                       
+                       // Skip zeros
+                       int i;
+                       for (i=0; i<buf.length; i++)
+                               if (buf[i] != 0)
+                                       break;
+                       
+                       if (i==0)   // No zeros to skip
+                               return buf;
+                       
+                       if (i < buf.length) {
+                               // Partially read
+                               int count = buf.length-i;  // No. of data bytes
+                               byte[] array = new byte[n];
+                               System.arraycopy(buf, i, array, 0, count);
+                               buf = read(n-count);
+                               if (DEBUG)
+                                       printBytes("      Received (partial)",buf);
+                               System.arraycopy(buf, 0, array, count, buf.length);
+                               
+                               if (DEBUG)
+                                       printBytes("    Returning",array);
+                               return array;
+                       }
+               }
+               
+               if (DEBUG)
+                       System.out.println("  No data read, returning empty");
+               return new byte[0];  // no data, only zeros
+       }
+       
+
+       private byte[] read(int n) throws IOException, UnsupportedCommOperationException {
+               byte[] bytes = new byte[n];
+               
+               port.enableReceiveThreshold(n);
+               
+               long t = System.currentTimeMillis() + TIMEOUT;
+               int count = 0;
+
+               if (DEBUG)
+                       System.out.println("    Reading "+n+" bytes");
+
+               while (count < n && System.currentTimeMillis() < t) {
+                       byte[] buf = new byte[n-count];
+                       int c = is.read(buf);
+                       System.arraycopy(buf, 0, bytes, count, c);
+                       count += c;
+               }
+               
+               byte[] array = new byte[count];
+               System.arraycopy(bytes, 0, array, 0, count);
+               
+               if (DEBUG)
+                       printBytes("    Returning",array);
+               
+               return array;
+       }
+       
+       private void write(String s) throws IOException {
+               write(s.getBytes(CHARSET));
+       }
+       
+       private void write(byte ... bytes) throws IOException {
+               if (DEBUG)
+                       printBytes("    Writing",bytes);
+               os.write(bytes);
+       }
+       
+       private void close() {
+               if (DEBUG)
+                       System.out.println("  Closing port");
+               
+               SerialPort p = port;
+               port = null;
+               is = null;
+               os = null;
+               if (p != null)
+                       p.close();
+       }
+       
+       
+       
+
+       
+       public static void main(String[] arg) {
+               
+               if (arg.length != 1) {
+                       System.err.println("Usage:  java Alt15K <basename>");
+                       System.err.println("Files will be saved <basename>-old.log, -med and -new");
+                       return;
+               }
+               
+               
+               String device = null;
+               String[] devices = Alt15K.getNames();
+               for (int i=0; i<devices.length; i++) {
+                       if (devices[i].matches(".*USB.*")) {
+                               device = devices[i];
+                               break;
+                       }
+               }
+               if (device == null) {
+                       System.out.println("Device not found.");
+                       return;
+               }
+               
+               
+               System.out.println("Selected device "+device);
+               
+               AltData alt = null;
+               String file;
+               try {
+                       Alt15K p = new Alt15K(device);
+
+                       System.out.println("Retrieving newest data...");
+                       alt = p.getData(0);
+                       System.out.println("Apogee at "+alt.getApogee()+" feet");
+
+                       file = arg[0]+"-new.log";
+                       System.out.println("Saving data to "+file+"...");
+                       savefile(file,alt);
+                       
+                       
+                       System.out.println("Retrieving medium data...");
+                       alt = p.getData(1);
+                       System.out.println("Apogee at "+alt.getApogee()+" feet");
+
+                       file = arg[0]+"-med.log";
+                       System.out.println("Saving data to "+file+"...");
+                       savefile(file,alt);
+                       
+                       
+                       System.out.println("Retrieving oldest data...");
+                       alt = p.getData(2);
+                       System.out.println("Apogee at "+alt.getApogee()+" feet");
+
+                       file = arg[0]+"-old.log";
+                       System.out.println("Saving data to "+file+"...");
+                       savefile(file,alt);
+                       
+               } catch (IOException e) {
+                       e.printStackTrace();
+               } catch (PortInUseException e) {
+                       e.printStackTrace();
+               }
+
+//             System.out.println(alt);
+//             alt.printData();
+               
+       }
+       
+       
+       static private void savefile(String file, AltData data) throws FileNotFoundException {
+               
+               PrintStream output = new PrintStream(file);
+               
+               // WTF is this so difficult?!?
+               DateFormat fmt = DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.MEDIUM);
+               TimeZone tz=TimeZone.getTimeZone("GMT+3");
+               fmt.setTimeZone(tz);
+
+               output.println("# Alt15K data, file "+file);
+               output.println("# Data retrieved at: "+fmt.format(new Date()));
+               output.println("# Values are in feet above launch level");
+               output.println("# ");
+               output.println("# Apogee = "+data.getApogee());
+               output.println("# MSL level = "+data.getMslLevel());
+               output.println("# Data count = "+data.getDataSamples());
+               
+               byte[] b = data.getVersion();
+               String s="";
+               for (int i=0; i<b.length; i++) {
+                       if (s.equals(""))
+                               s = ""+((int)b[i]);
+                       else 
+                               s = s+"."+((int)b[i]);
+               }
+               output.println("# Altimeter version = " + s);
+               
+               int[] values = data.getData();
+               for (int i=0; i < values.length; i++) {
+                       output.println(""+values[i]);
+               }
+               
+               output.close();
+       }
+       
+       
+       static private void printBytes(String str, byte[] b) {
+               printBytes(str, b,b.length);
+       }
+       
+       static private void printBytes(String str, byte[] b, int n) {
+               String s;
+               s = str+" "+n+" bytes:";
+               for (int i=0; i<n; i++) {
+                       s += " "+unsign(b[i]);
+               }
+               System.out.println(s);
+       }
+       
+       static private int unsign(byte b) {
+               if (b >= 0)
+                       return b;
+               else
+                       return 256 + b;
+       }
+       
+       @SuppressWarnings("unused")
+       static private int combine(int a, int b) {
+               return 256*a + b;
+       }
+       
+       static private int combine(byte a, byte b) {
+               int val = 256*unsign(a)+unsign(b);
+               if (val <= 32767)
+                       return val;
+               else
+                       return val-65536;
+                       
+       }
+       
+}
diff --git a/src-extra/altimeter/AltData.java b/src-extra/altimeter/AltData.java
new file mode 100644 (file)
index 0000000..63314c7
--- /dev/null
@@ -0,0 +1,81 @@
+package altimeter;
+
+public class AltData {
+
+       private int mslLevel = 0;
+       private int samples = 0;
+       private int[] data = null;
+       private byte[] version = null;
+       
+       
+       public void setMslLevel(int msl) {
+               mslLevel = msl;
+       }
+       public int getMslLevel() {
+               return mslLevel;
+       }
+       
+       public void setDataSamples(int s) {
+               samples = s;
+       }
+       public int getDataSamples() {
+               return samples;
+       }
+       
+       public void setVersion(byte[] v) {
+               if (v==null)
+                       version = null;
+               else 
+                       version = v.clone();
+       }
+       public byte[] getVersion() {
+               if (version == null)
+                       return null;
+               return version.clone();         
+       }
+       
+       public void setData(int[] data) {
+               if (data==null)
+                       this.data = null;
+               else 
+                       this.data = data.clone();
+       }
+       public int[] getData() {
+               if (data == null)
+                       return null;
+               return data.clone();
+       }
+
+       public int getApogee() {
+               if (data == null || data.length==0)
+                       return 0;
+               int max = Integer.MIN_VALUE;
+               for (int i=0; i<data.length; i++) {
+                       if (data[i] > max)
+                               max = data[i];
+               }
+               return max;
+       }
+       
+       @Override
+       public String toString() {
+               String s = "AltData(";
+               s += "MSL:"+getMslLevel()+",";
+               s += "Apogee:"+getApogee()+",";
+               s += "Samples:"+getDataSamples();
+               s += ")";
+               return s;
+       }
+       
+       public void printData() {
+               System.out.println(toString()+":");
+               for (int i=0; i<data.length; i+=8) {
+                       String s = "  "+i+":";
+                       for (int j=0; j<8 && (i+j)<data.length; j++) {
+                               s += " "+data[i+j];
+                       }
+                       System.out.println(s);
+               }
+       }
+       
+}
diff --git a/src-extra/altimeter/RotationLogger.java b/src-extra/altimeter/RotationLogger.java
new file mode 100644 (file)
index 0000000..45e7d76
--- /dev/null
@@ -0,0 +1,356 @@
+package altimeter;
+
+import gnu.io.CommPortIdentifier;
+import gnu.io.PortInUseException;
+import gnu.io.SerialPort;
+import gnu.io.UnsupportedCommOperationException;
+
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.ArrayList;
+import java.util.Enumeration;
+
+/**
+ * Class to interface the PerfectFlite Alt15K/WD altimeter.
+ * 
+ * Also includes a main method that retrieves all flight profiles and saves them to files.
+ * 
+ * @author Sampo Niskanen <sampo.niskanen@iki.fi>
+ */
+
+public class RotationLogger {
+       private static final boolean DEBUG = false;
+       
+       private static final int BYTES = 65536; 
+       
+       
+       private final CommPortIdentifier portID;
+       private SerialPort port = null;
+       private InputStream is = null;
+       private OutputStream os = null;
+       
+       
+
+       @SuppressWarnings("unchecked")
+       public static String[] getNames() {
+               ArrayList<String> list = new ArrayList<String>();;
+               
+               Enumeration pids = CommPortIdentifier.getPortIdentifiers();
+
+               while (pids.hasMoreElements()) {
+                   CommPortIdentifier pid = (CommPortIdentifier) pids.nextElement();
+
+                   if (pid.getPortType() == CommPortIdentifier.PORT_SERIAL)
+                       list.add(pid.getName());
+               }
+               return list.toArray(new String[0]);
+       }
+
+       
+       
+       
+
+       @SuppressWarnings("unchecked")
+       public RotationLogger(String name) throws IOException {
+               CommPortIdentifier portID = null;
+               
+               Enumeration portIdentifiers = CommPortIdentifier.getPortIdentifiers();
+               while (portIdentifiers.hasMoreElements()) {
+                   CommPortIdentifier pid = (CommPortIdentifier) portIdentifiers.nextElement();
+                   
+                   if(pid.getPortType() == CommPortIdentifier.PORT_SERIAL &&
+                      pid.getName().equals(name)) {
+                       portID = pid;
+                       break;
+                   }
+               }
+               
+               if (portID==null) {
+                       throw new IOException("Port '"+name+"' not found.");
+               }
+               this.portID = portID;
+       }
+       
+       
+       
+       
+       
+       
+       public void readData() throws IOException, PortInUseException {
+               int c;
+               
+               int[] data = new int[BYTES];
+               
+               FileOutputStream rawdump = null;
+               
+               
+               try {
+                       open();
+
+                       System.err.println("Sending dump mode command...");
+                       
+                       for (int i=0; i<16; i++) {
+                               os.write('D');
+                               try {
+                                       Thread.sleep(10);
+                               } catch (InterruptedException ignore) { }
+                       }
+                       
+                       System.err.println("Waiting for response...");
+                       while (true) {
+                               c = is.read();
+                               if (c == 'K') {
+                                       break;
+                               } else {
+                                       System.err.printf("Received spurious c=%d\n",c);
+                               }
+                       }
+                       
+                       System.err.println("Received response.");
+                       
+
+                       
+                       System.err.println("Opening 'rawdump'...");
+                       rawdump = new FileOutputStream("rawdump");
+                       
+                       
+                       
+                       System.err.println("Performing dump...");
+
+                       os.write('A');
+
+                       byte[] buffer = new byte[1024];
+                       int printCount = 0;
+                       for (int count=0; count < BYTES; ) {
+                               if ((BYTES-count) < buffer.length) {
+                                       buffer = new byte[BYTES-count];
+                               }
+                               
+                               int n = is.read(buffer);
+                               if (n < 0) {
+                                       System.err.println("Error condition, n="+n);
+                                       return;
+                               }
+                       
+                               rawdump.write(buffer, 0, n);
+                               
+                               for (int i=0; i<n; i++) {
+                                       data[count+i] = unsign(buffer[i]);
+                               }
+                               count += n;
+                               if (count - printCount > 1024) {
+                                       System.err.println("Read "+count+" bytes...");
+                                       printCount = count;
+                               }
+                       }
+
+
+                       System.err.println("Verifying checksum...");
+                       int reported = is.read();
+                       
+                       byte computed = 0;
+                       for (int i=0; i < data.length; i++) {
+                               computed += data[i];
+                       }
+                       if (computed == reported) {
+                               System.err.println("Checksum ok ("+computed+")");
+                       } else {
+                               System.err.println("Error in checksum, computed="+computed+
+                                               " reported="+reported);
+                       }
+                       
+                       System.err.println("Communication done.");
+                       
+               } catch (UnsupportedCommOperationException e) {
+                       // TODO Auto-generated catch block
+                       e.printStackTrace();
+               } finally {
+                       close();
+                       if (rawdump != null)
+                               rawdump.close();
+               }
+               
+               convertData(data);
+               
+       }
+       
+       
+       
+       ////////////  Data interpretation   //////////////      
+       
+       
+       private static void convertData(int[] data) {
+
+               System.err.println("Converting data...");
+
+               int lastBuffer = data[0xffff];
+               if (lastBuffer < 0 || lastBuffer > 3) {
+                       System.err.println("Illegal last accessed buffer: "+lastBuffer);
+                       return;
+               }
+               System.err.println("Last used buffer: "+lastBuffer);
+               
+               for (int i=4; i>0; i--) {
+                       int n = (lastBuffer + i) % 4;
+                       int bufNumber = 4-i;
+                       
+                       convertBuffer(data, n * (BYTES/4), bufNumber);
+               }
+               
+       }
+       
+       
+       private static void convertBuffer(int[] data, int position, int bufNumber) {
+               int startPosition;
+               
+               startPosition = data[position + 0xfd] << 8 + data[position+0xfe];
+
+               // 50 samples per 128 bytes 
+               int startTime = (startPosition -position) * 50 / 128;
+               
+               System.err.println("  Buffer "+ bufNumber + " (at position "+position+")...");
+               System.err.println("  Start position "+startPosition+" time "+startTime);
+
+               System.out.println("# Buffer "+bufNumber);
+               System.out.println("# Start position t="+startTime);
+               
+               
+               int t = 0;
+               for (int page = 0; page < 128; page++) {
+                       int pageStart = position + page * 128;
+
+                       if (pageStart == startPosition) {
+                               System.out.println("# ---clip---");
+                       }
+
+                       for (int i=0; i<125; i += 5) {
+                               int sample1, sample2;
+                               
+                               int start = pageStart + i;
+//                             System.err.println("page="+page+" i="+i+
+//                                             " position="+position+" pageStart="+pageStart+" start="+start);
+                               
+                               sample1 = (data[start] << 2) + (data[start+1] >> 6);
+                               sample2 = ((data[start+1] & 0x3f) << 4) + (data[start+2] >> 4);
+                               System.out.printf("%d  %4d  %4d %4d\n", bufNumber, t, sample1, sample2);
+                               t++;
+                               
+                               sample1 = ((data[start+2] & 0x0f) << 6) + (data[start+3] >> 2);
+                               sample2 = ((data[start+3] & 3) << 8) + data[start+4];
+                               System.out.printf("%d  %4d  %4d %4d\n", bufNumber, t, sample1, sample2);
+                               t++;
+                       }
+               }
+       }
+
+       
+       
+       private void open() throws PortInUseException, IOException, 
+                       UnsupportedCommOperationException {
+               
+               if (port != null) {
+                       System.err.println("ERROR: open() called with port="+port);
+                       Thread.dumpStack();
+                       close();
+               }
+               
+               if (DEBUG) {
+                       System.err.println("  Opening port...");
+               }
+
+               port = (SerialPort)portID.open("OpenRocket",1000);
+               
+               port.setSerialPortParams(9600, SerialPort.DATABITS_8, SerialPort.STOPBITS_1, 
+                               SerialPort.PARITY_NONE);
+
+               port.setInputBufferSize(1);
+               port.setOutputBufferSize(1);
+
+               is = port.getInputStream();
+               os = port.getOutputStream();
+       }
+       
+       
+       private void close() {
+               if (DEBUG)
+                       System.err.println("  Closing port");
+               
+               SerialPort p = port;
+               port = null;
+               is = null;
+               if (p != null)
+                       p.close();
+       }
+       
+       
+       
+       private static int unsign(byte b) {
+               if (b >= 0)
+                       return b;
+               else
+               return 256 + b;
+       }
+       
+       
+
+       
+       public static void main(String[] arg) throws Exception {
+               
+               if (arg.length > 2) {
+                       System.err.println("Illegal arguments.");
+                       return;
+               }
+               if (arg.length == 1) {
+                       FileInputStream is = new FileInputStream(arg[0]);
+                       byte[] buffer = new byte[BYTES];
+                       int n = is.read(buffer);
+                       if (n != BYTES) {
+                               System.err.println("Could read only "+n+" bytes");
+                               return;
+                       }
+                       
+                       int[] data = new int[BYTES];
+                       for (int i=0; i<BYTES; i++) {
+                               data[i] = unsign(buffer[i]);
+                       }
+
+                       int checksum=0;
+                       for (int i=0; i<BYTES; i++) {
+                               checksum += data[i];
+                       }
+                       checksum = checksum%256;
+                       System.err.println("Checksum: "+checksum);
+                       
+                       convertData(data);
+                       return;                 
+               }
+               
+               
+               String device = null;
+               String[] devices = RotationLogger.getNames();
+               for (int i=0; i<devices.length; i++) {
+                       if (devices[i].matches(".*USB.*")) {
+                               device = devices[i];
+                               break;
+                       }
+               }
+               if (device == null) {
+                       System.err.println("Device not found.");
+                       return;
+               }
+               
+               
+               System.err.println("Selected device "+device);
+               
+               
+               RotationLogger p = new RotationLogger(device);
+               
+               p.readData();
+               
+       }
+       
+       
+}
diff --git a/src-extra/altimeter/SerialDownload.java b/src-extra/altimeter/SerialDownload.java
new file mode 100644 (file)
index 0000000..d82e582
--- /dev/null
@@ -0,0 +1,184 @@
+package altimeter;
+
+import gnu.io.CommPortIdentifier;
+import gnu.io.PortInUseException;
+import gnu.io.SerialPort;
+import gnu.io.UnsupportedCommOperationException;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.Enumeration;
+
+/**
+ * Class to interface the PerfectFlite Alt15K/WD altimeter.
+ * 
+ * Also includes a main method that retrieves all flight profiles and saves them to files.
+ * 
+ * @author Sampo Niskanen <sampo.niskanen@iki.fi>
+ */
+
+public class SerialDownload {
+       private static final boolean DEBUG = false;
+       
+       private static final int MAGIC = 666;
+       
+       private final CommPortIdentifier portID;
+       private SerialPort port = null;
+       private InputStream is = null;
+       
+       
+
+       @SuppressWarnings("unchecked")
+       public static String[] getNames() {
+               ArrayList<String> list = new ArrayList<String>();;
+               
+               Enumeration pids = CommPortIdentifier.getPortIdentifiers();
+
+               while (pids.hasMoreElements()) {
+                   CommPortIdentifier pid = (CommPortIdentifier) pids.nextElement();
+
+                   if (pid.getPortType() == CommPortIdentifier.PORT_SERIAL)
+                       list.add(pid.getName());
+               }
+               return list.toArray(new String[0]);
+       }
+
+       
+       
+       
+
+       @SuppressWarnings("unchecked")
+       public SerialDownload(String name) throws IOException {
+               CommPortIdentifier portID = null;
+               
+               Enumeration portIdentifiers = CommPortIdentifier.getPortIdentifiers();
+               while (portIdentifiers.hasMoreElements()) {
+                   CommPortIdentifier pid = (CommPortIdentifier) portIdentifiers.nextElement();
+                   
+                   if(pid.getPortType() == CommPortIdentifier.PORT_SERIAL &&
+                      pid.getName().equals(name)) {
+                       portID = pid;
+                       break;
+                   }
+               }
+               
+               if (portID==null) {
+                       throw new IOException("Port '"+name+"' not found.");
+               }
+               this.portID = portID;
+       }
+       
+       
+       
+       
+       
+       
+       public void readData() throws IOException, PortInUseException {
+               long t0 = -1;
+               long t;
+               
+               int previous = MAGIC;
+               
+               
+               try {
+                       open();
+                       
+                       System.err.println("Ready to read...");
+                       while (true) {
+                               int c = is.read();
+                               t = System.nanoTime();
+                               if (t0 < 0)
+                                       t0 = t;
+                               
+                               System.out.printf("%10.6f %d\n", ((double)t-t0)/1000000000.0, c);
+                               
+                               if (previous == MAGIC) {
+                                       previous = c;
+                               } else {
+                                       System.out.printf("# Altitude: %5d\n", previous*256 + c);
+                                       previous = MAGIC;
+                               }
+                               
+                               if (c < 0)
+                                       break;
+                       }
+                       
+                       
+               } catch (UnsupportedCommOperationException e) {
+                       // TODO Auto-generated catch block
+                       e.printStackTrace();
+               } finally {
+                       close();
+               }
+       }
+
+       
+       
+       private void open() throws PortInUseException, IOException, 
+                       UnsupportedCommOperationException {
+               
+               if (port != null) {
+                       System.err.println("ERROR: open() called with port="+port);
+                       Thread.dumpStack();
+                       close();
+               }
+               
+               if (DEBUG) {
+                       System.err.println("  Opening port...");
+               }
+
+               port = (SerialPort)portID.open("OpenRocket",1000);
+               
+               port.setSerialPortParams(9600, SerialPort.DATABITS_8, SerialPort.STOPBITS_1, 
+                               SerialPort.PARITY_NONE);
+
+               port.setInputBufferSize(1);
+               port.setOutputBufferSize(1);
+
+               is = port.getInputStream();
+       }
+       
+       
+       private void close() {
+               if (DEBUG)
+                       System.err.println("  Closing port");
+               
+               SerialPort p = port;
+               port = null;
+               is = null;
+               if (p != null)
+                       p.close();
+       }
+       
+       
+       
+
+       
+       public static void main(String[] arg) throws Exception {
+               
+               String device = null;
+               String[] devices = SerialDownload.getNames();
+               for (int i=0; i<devices.length; i++) {
+                       if (devices[i].matches(".*USB.*")) {
+                               device = devices[i];
+                               break;
+                       }
+               }
+               if (device == null) {
+                       System.err.println("Device not found.");
+                       return;
+               }
+               
+               
+               System.err.println("Selected device "+device);
+               
+               
+               SerialDownload p = new SerialDownload(device);
+               
+               p.readData();
+               
+       }
+       
+       
+}
diff --git a/src/net/sf/openrocket/communication/BugReporter.java b/src/net/sf/openrocket/communication/BugReporter.java
new file mode 100644 (file)
index 0000000..048701a
--- /dev/null
@@ -0,0 +1,58 @@
+package net.sf.openrocket.communication;
+
+import java.io.IOException;
+import java.io.OutputStreamWriter;
+import java.net.HttpURLConnection;
+
+import net.sf.openrocket.util.Prefs;
+
+public class BugReporter extends Communicator {
+       
+       // Inhibit instantiation
+       private BugReporter() {
+       }
+
+       
+       /**
+        * Send the provided report to the OpenRocket bug report URL.  If the connection
+        * fails or the server does not respond with the correct response code, an
+        * exception is thrown.
+        * 
+        * @param report                the report to send.
+        * @throws IOException  if an error occurs while connecting to the server or
+        *                                              the server responds with a wrong response code.
+        */
+       public static void sendBugReport(String report) throws IOException {
+               
+               HttpURLConnection connection = connectionSource.getConnection(BUG_REPORT_URL);
+               
+               connection.setConnectTimeout(CONNECTION_TIMEOUT);
+               connection.setInstanceFollowRedirects(true);
+               connection.setRequestMethod("POST");
+               connection.setUseCaches(false);
+               connection.setRequestProperty("X-OpenRocket-Version", encode(Prefs.getVersion()));
+               
+               String post;
+               post = (VERSION_PARAM + "=" + encode(Prefs.getVersion())
+                               + "&" + BUG_REPORT_PARAM + "=" + encode(report));
+               
+               OutputStreamWriter wr = null;
+               try {
+                       // Send post information
+                       connection.setDoOutput(true);
+                       wr = new OutputStreamWriter(connection.getOutputStream(), "UTF-8");
+                       wr.write(post);
+                       wr.flush();
+                       
+                       if (connection.getResponseCode() != BUG_REPORT_RESPONSE_CODE) {
+                               throw new IOException("Server responded with code " + 
+                                               connection.getResponseCode() + ", expecting " + BUG_REPORT_RESPONSE_CODE);
+                       }
+               } finally {
+                       if (wr != null)
+                               wr.close();
+                       connection.disconnect();
+               }
+       }
+       
+}
diff --git a/src/net/sf/openrocket/communication/Communication.java b/src/net/sf/openrocket/communication/Communication.java
deleted file mode 100644 (file)
index df3b0ac..0000000
+++ /dev/null
@@ -1,262 +0,0 @@
-package net.sf.openrocket.communication;
-
-import java.io.BufferedReader;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.InputStreamReader;
-import java.io.OutputStreamWriter;
-import java.io.Reader;
-import java.io.UnsupportedEncodingException;
-import java.net.HttpURLConnection;
-import java.net.URL;
-import java.net.URLEncoder;
-import java.util.ArrayList;
-
-import net.sf.openrocket.util.ComparablePair;
-import net.sf.openrocket.util.Prefs;
-
-public class Communication {
-
-       private static final String BUG_REPORT_URL = 
-               "http://openrocket.sourceforge.net/actions/reportbug";
-       private static final String UPDATE_INFO_URL =
-               "http://openrocket.sourceforge.net/actions/updates";
-
-       private static final String VERSION_PARAM = "version";
-       
-       
-       private static final String BUG_REPORT_PARAM = "content";
-       private static final int BUG_REPORT_RESPONSE_CODE = HttpURLConnection.HTTP_ACCEPTED;
-       private static final int CONNECTION_TIMEOUT = 10000;  // in milliseconds
-
-       private static final int UPDATE_INFO_UPDATE_AVAILABLE = HttpURLConnection.HTTP_OK;
-       private static final int UPDATE_INFO_NO_UPDATE_CODE = HttpURLConnection.HTTP_NO_CONTENT;
-       private static final String UPDATE_INFO_CONTENT_TYPE = "text/plain";
-
-       
-       private static UpdateInfoFetcher fetcher = null;
-       
-
-       /**
-        * Send the provided report to the OpenRocket bug report URL.  If the connection
-        * fails or the server does not respond with the correct response code, an
-        * exception is thrown.
-        * 
-        * @param report                the report to send.
-        * @throws IOException  if an error occurs while connecting to the server or
-        *                                              the server responds with a wrong response code.
-        */
-       public static void sendBugReport(String report) throws IOException {
-               URL url = new URL(BUG_REPORT_URL);
-               
-               HttpURLConnection connection = (HttpURLConnection) url.openConnection();
-               
-               connection.setConnectTimeout(CONNECTION_TIMEOUT);
-               connection.setInstanceFollowRedirects(true);
-               connection.setRequestMethod("POST");
-               connection.setUseCaches(false);
-               connection.setRequestProperty("X-OpenRocket-Version", encode(Prefs.getVersion()));
-               
-               String post;
-               post = (VERSION_PARAM + "=" + encode(Prefs.getVersion())
-                               + "&" + BUG_REPORT_PARAM + "=" + encode(report));
-               
-               OutputStreamWriter wr = null;
-               try {
-                       // Send post information
-                       connection.setDoOutput(true);
-                       wr = new OutputStreamWriter(connection.getOutputStream(), "UTF-8");
-                       wr.write(post);
-                       wr.flush();
-                       
-                       if (connection.getResponseCode() != BUG_REPORT_RESPONSE_CODE) {
-                               throw new IOException("Server responded with code " + 
-                                               connection.getResponseCode() + ", expecting " + BUG_REPORT_RESPONSE_CODE);
-                       }
-               } finally {
-                       if (wr != null)
-                               wr.close();
-                       connection.disconnect();
-               }
-       }
-       
-       
-       
-       /**
-        * Start an asynchronous task that will fetch information about the latest
-        * OpenRocket version.  This will overwrite any previous fetching operation.
-        */
-       public static void startFetchUpdateInfo() {
-               fetcher = new UpdateInfoFetcher();
-               fetcher.start();
-       }
-       
-       
-       /**
-        * Check whether the update info fetching is still in progress.
-        * 
-        * @return      <code>true</code> if the communication is still in progress.
-        */
-       public static boolean isFetchUpdateInfoRunning() {
-               if (fetcher == null) {
-                       throw new IllegalStateException("startFetchUpdateInfo() has not been called");
-               }
-               return fetcher.isAlive();
-       }
-       
-       
-       /**
-        * Retrieve the result of the background update info fetcher.  This method returns 
-        * the result of the previous call to {@link #startFetchUpdateInfo()}. It must be
-        * called before calling this method.
-        * <p>
-        * This method will return <code>null</code> if the info fetcher is still running or
-        * if it encountered a problem in communicating with the server.  The difference can
-        * be checked using {@link #isFetchUpdateInfoRunning()}.
-        * 
-        * @return      the update result, or <code>null</code> if the fetching is still in progress
-        *                      or an error occurred while communicating with the server.
-        * @throws      IllegalStateException   if {@link #startFetchUpdateInfo()} has not been called.
-        */
-       public static UpdateInfo getUpdateInfo() {
-               if (fetcher == null) {
-                       throw new IllegalStateException("startFetchUpdateInfo() has not been called");
-               }
-               return fetcher.info;
-       }
-       
-       
-       
-       /**
-        * Parse the data received from the server.
-        * 
-        * @param r             the Reader from which to read.
-        * @return              an UpdateInfo construct, or <code>null</code> if the data was invalid.
-        * @throws IOException  if an I/O exception occurs.
-        */
-       /* package-private */
-       static UpdateInfo parseUpdateInput(Reader r) throws IOException {
-               BufferedReader reader;
-               if (r instanceof BufferedReader) {
-                       reader = (BufferedReader)r;
-               } else {
-                       reader = new BufferedReader(r);
-               }
-               
-               
-               String version = null;
-               ArrayList<ComparablePair<Integer,String>> updates = 
-                       new ArrayList<ComparablePair<Integer,String>>();
-               
-               String str = reader.readLine();
-               while (str != null) {
-                       if (str.matches("^Version: *[0-9]+\\.[0-9]+\\.[0-9]+[a-zA-Z0-9.-]* *$")) {
-                               version = str.substring(8).trim();
-                       } else if (str.matches("^[0-9]+:\\p{Print}+$")) {
-                               int index = str.indexOf(':');
-                               int value = Integer.parseInt(str.substring(0, index));
-                               String desc = str.substring(index+1).trim();
-                               if (!desc.equals("")) {
-                                       updates.add(new ComparablePair<Integer,String>(value, desc));
-                               }
-                       }
-                       // Ignore anything else
-                       str = reader.readLine();
-               }
-               
-               if (version != null) {
-                       return new UpdateInfo(version, updates);
-               } else {
-                       return null;
-               }
-       }
-       
-       
-       
-       
-       private static class UpdateInfoFetcher extends Thread {
-
-               private volatile UpdateInfo info = null;
-               
-               @Override
-               public void run() {
-                       try {
-                               doConnection();
-                       } catch (IOException e) {
-                               return;
-                       }
-               }
-               
-               
-               private void doConnection() throws IOException {
-                       URL url;
-                       url = new URL(UPDATE_INFO_URL + "?" + VERSION_PARAM + "=" + 
-                                       encode(Prefs.getVersion()));
-                       
-                       HttpURLConnection connection = (HttpURLConnection) url.openConnection();
-                       
-                       connection.setConnectTimeout(CONNECTION_TIMEOUT);
-                       connection.setInstanceFollowRedirects(true);
-                       connection.setRequestMethod("GET");
-                       connection.setUseCaches(false);
-                       connection.setRequestProperty("X-OpenRocket-Version", encode(Prefs.getVersion()));
-                       connection.setRequestProperty("X-OpenRocket-ID", encode(Prefs.getUniqueID()));
-                       connection.setRequestProperty("X-OpenRocket-OS", encode(
-                                       System.getProperty("os.name") + " " + System.getProperty("os.arch")));
-                       connection.setRequestProperty("X-OpenRocket-Java", encode(
-                                       System.getProperty("java.vendor") + " " + System.getProperty("java.version")));
-                       connection.setRequestProperty("X-OpenRocket-Country", encode(
-                                       System.getProperty("user.country")));
-                       
-                       InputStream is = null;
-                       try {
-                               connection.connect();
-                               
-                               if (connection.getResponseCode() == UPDATE_INFO_NO_UPDATE_CODE) {
-                                       // No updates are available
-                                       info = new UpdateInfo();
-                                       return;
-                               }
-                               
-                               if (connection.getResponseCode() != UPDATE_INFO_UPDATE_AVAILABLE) {
-                                       // Error communicating with server
-                                       return;
-                               }
-                               
-                               if (!UPDATE_INFO_CONTENT_TYPE.equalsIgnoreCase(connection.getContentType())) {
-                                       // Unknown response type
-                                       return;
-                               }
-                               
-                               // Update is available, parse input
-                               is = connection.getInputStream();
-                               String encoding = connection.getContentEncoding();
-                               if (encoding == null)
-                                       encoding = "UTF-8";
-                               BufferedReader reader = new BufferedReader(new InputStreamReader(is, encoding));
-                               
-                               
-
-                       } finally {
-                               if (is != null)
-                                       is.close();
-                               connection.disconnect();
-                       }
-
-                       
-               }
-               
-       }
-       
-       
-       private static String encode(String str) {
-               if (str == null)
-                       return "null";
-               try {
-                       return URLEncoder.encode(str, "UTF-8");
-               } catch (UnsupportedEncodingException e) {
-                       throw new RuntimeException("Unsupported encoding UTF-8", e);
-               }
-       }
-
-}
diff --git a/src/net/sf/openrocket/communication/Communicator.java b/src/net/sf/openrocket/communication/Communicator.java
new file mode 100644 (file)
index 0000000..24ed1f5
--- /dev/null
@@ -0,0 +1,72 @@
+package net.sf.openrocket.communication;
+
+import java.io.UnsupportedEncodingException;
+import java.net.HttpURLConnection;
+import java.net.URLEncoder;
+
+public abstract class Communicator {
+
+       protected static final String BUG_REPORT_URL;
+
+       protected static final String UPDATE_INFO_URL;
+       
+       static {
+               String url;
+               url = System.getProperty("openrocket.debug.bugurl");
+               if (url == null)
+                       url = "http://openrocket.sourceforge.net/actions/reportbug";
+               BUG_REPORT_URL = url;
+               
+               url = System.getProperty("openrocket.debug.updateurl");
+               if (url == null)
+                       url = "http://openrocket.sourceforge.net/actions/updates";
+               UPDATE_INFO_URL = url;
+       }
+       
+
+       protected static final String VERSION_PARAM = "version";
+       
+
+       protected static final String BUG_REPORT_PARAM = "content";
+       protected static final int BUG_REPORT_RESPONSE_CODE = HttpURLConnection.HTTP_ACCEPTED;
+       protected static final int CONNECTION_TIMEOUT = 10000;  // in milliseconds
+
+       protected static final int UPDATE_INFO_UPDATE_AVAILABLE = HttpURLConnection.HTTP_OK;
+       protected static final int UPDATE_INFO_NO_UPDATE_CODE = HttpURLConnection.HTTP_NO_CONTENT;
+       protected static final String UPDATE_INFO_CONTENT_TYPE = "text/plain";
+
+       // Limit the number of bytes that can be read from the server
+       protected static final int MAX_INPUT_BYTES = 20000;
+
+       
+       protected static ConnectionSource connectionSource = new DefaultConnectionSource();
+       
+       
+       /**
+        * Set the source of the network connections.  This can be used for unit testing.
+        * By default the source is a DefaultConnectionSource.
+        * 
+        * @param source        the source of the connections.
+        */
+       public static void setConnectionSource(ConnectionSource source) {
+               connectionSource = source;
+       }
+       
+
+       /**
+        * URL-encode the specified string in UTF-8 encoding.
+        * 
+        * @param str   the string to encode (null ok)
+        * @return              the encoded string or "null"
+        */
+       public static String encode(String str) {
+               if (str == null)
+                       return "null";
+               try {
+                       return URLEncoder.encode(str, "UTF-8");
+               } catch (UnsupportedEncodingException e) {
+                       throw new RuntimeException("Unsupported encoding UTF-8", e);
+               }
+       }
+       
+}
diff --git a/src/net/sf/openrocket/communication/ConnectionSource.java b/src/net/sf/openrocket/communication/ConnectionSource.java
new file mode 100644 (file)
index 0000000..b5b0fdc
--- /dev/null
@@ -0,0 +1,21 @@
+package net.sf.openrocket.communication;
+
+import java.io.IOException;
+import java.net.HttpURLConnection;
+
+/**
+ * A source for network connections.  This interface exists to enable unit testing.
+ * 
+ * @author Sampo Niskanen <sampo.niskanen@iki.fi>
+ */
+public interface ConnectionSource {
+
+       /**
+        * Return a connection to the specified url.
+        * @param url   the URL to connect to.
+        * @return              the corresponding HttpURLConnection
+        * @throws IOException  if an IOException occurs
+        */
+       public HttpURLConnection getConnection(String url) throws IOException;
+       
+}
diff --git a/src/net/sf/openrocket/communication/DefaultConnectionSource.java b/src/net/sf/openrocket/communication/DefaultConnectionSource.java
new file mode 100644 (file)
index 0000000..501c37a
--- /dev/null
@@ -0,0 +1,21 @@
+package net.sf.openrocket.communication;
+
+import java.io.IOException;
+import java.net.HttpURLConnection;
+import java.net.URL;
+
+/**
+ * Default implementation of ConnectionSource, which simply opens a new
+ * HttpURLConnection from a URL object.
+ * 
+ * @author Sampo Niskanen <sampo.niskanen@iki.fi>
+ */
+public class DefaultConnectionSource implements ConnectionSource {
+
+       @Override
+       public HttpURLConnection getConnection(String urlString) throws IOException {
+               URL url = new URL(urlString);
+               return (HttpURLConnection) url.openConnection();
+       }
+
+}
diff --git a/src/net/sf/openrocket/communication/UpdateInfoRetriever.java b/src/net/sf/openrocket/communication/UpdateInfoRetriever.java
new file mode 100644 (file)
index 0000000..d67ea33
--- /dev/null
@@ -0,0 +1,226 @@
+package net.sf.openrocket.communication;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.Reader;
+import java.net.HttpURLConnection;
+import java.util.ArrayList;
+
+import net.sf.openrocket.util.ComparablePair;
+import net.sf.openrocket.util.LimitedInputStream;
+import net.sf.openrocket.util.Prefs;
+
+public class UpdateInfoRetriever {
+
+       private UpdateInfoFetcher fetcher = null;
+       
+       
+       /**
+        * Start an asynchronous task that will fetch information about the latest
+        * OpenRocket version.  This will overwrite any previous fetching operation.
+        * This call will return immediately.
+        */
+       public void start() {
+               fetcher = new UpdateInfoFetcher();
+               fetcher.setDaemon(true);
+               fetcher.start();
+       }
+       
+       
+       /**
+        * Check whether the update info fetching is still in progress.
+        * 
+        * @return      <code>true</code> if the communication is still in progress.
+        */
+       public boolean isRunning() {
+               if (fetcher == null) {
+                       throw new IllegalStateException("startFetchUpdateInfo() has not been called");
+               }
+               return fetcher.isAlive();
+       }
+       
+       
+       /**
+        * Retrieve the result of the background update info fetcher.  This method returns 
+        * the result of the previous call to {@link #start()}. It must be
+        * called before calling this method.
+        * <p>
+        * This method will return <code>null</code> if the info fetcher is still running or
+        * if it encountered a problem in communicating with the server.  The difference can
+        * be checked using {@link #isRunning()}.
+        * 
+        * @return      the update result, or <code>null</code> if the fetching is still in progress
+        *                      or an error occurred while communicating with the server.
+        * @throws      IllegalStateException   if {@link #start()} has not been called.
+        */
+       public UpdateInfo getUpdateInfo() {
+               if (fetcher == null) {
+                       throw new IllegalStateException("start() has not been called");
+               }
+               return fetcher.info;
+       }
+       
+       
+       
+       /**
+        * Parse the data received from the server.
+        * 
+        * @param r             the Reader from which to read.
+        * @return              an UpdateInfo construct, or <code>null</code> if the data was invalid.
+        * @throws IOException  if an I/O exception occurs.
+        */
+       /* package-private */
+       static UpdateInfo parseUpdateInput(Reader r) throws IOException {
+               BufferedReader reader;
+               if (r instanceof BufferedReader) {
+                       reader = (BufferedReader)r;
+               } else {
+                       reader = new BufferedReader(r);
+               }
+               
+               
+               String version = null;
+               ArrayList<ComparablePair<Integer,String>> updates = 
+                       new ArrayList<ComparablePair<Integer,String>>();
+               
+               String str = reader.readLine();
+               while (str != null) {
+                       if (str.matches("^Version: *[0-9]+\\.[0-9]+\\.[0-9]+[a-zA-Z0-9.-]* *$")) {
+                               version = str.substring(8).trim();
+                       } else if (str.matches("^[0-9]+:\\p{Print}+$")) {
+                               int index = str.indexOf(':');
+                               int value = Integer.parseInt(str.substring(0, index));
+                               String desc = str.substring(index+1).trim();
+                               if (!desc.equals("")) {
+                                       updates.add(new ComparablePair<Integer,String>(value, desc));
+                               }
+                       }
+                       // Ignore anything else
+                       str = reader.readLine();
+               }
+               
+               if (version != null) {
+                       return new UpdateInfo(version, updates);
+               } else {
+                       return null;
+               }
+       }
+       
+       
+       
+       /**
+        * An asynchronous task that fetches and parses the update info.
+        * 
+        * @author Sampo Niskanen <sampo.niskanen@iki.fi>
+        */
+       private class UpdateInfoFetcher extends Thread {
+
+               private volatile UpdateInfo info = null;
+               
+               @Override
+               public void run() {
+                       try {
+                               doConnection();
+                       } catch (IOException e) {
+                               return;
+                       }
+               }
+               
+               
+               private void doConnection() throws IOException {
+                       String url = Communicator.UPDATE_INFO_URL + "?" + Communicator.VERSION_PARAM + "=" 
+                                       + Communicator.encode(Prefs.getVersion());
+                       
+                       HttpURLConnection connection = Communicator.connectionSource.getConnection(url);
+                       
+                       connection.setConnectTimeout(Communicator.CONNECTION_TIMEOUT);
+                       connection.setInstanceFollowRedirects(true);
+                       connection.setRequestMethod("GET");
+                       connection.setUseCaches(false);
+                       connection.setDoInput(true);
+                       connection.setRequestProperty("X-OpenRocket-Version", 
+                                       Communicator.encode(Prefs.getVersion()));
+                       connection.setRequestProperty("X-OpenRocket-ID", 
+                                       Communicator.encode(Prefs.getUniqueID()));
+                       connection.setRequestProperty("X-OpenRocket-OS", 
+                                       Communicator.encode(System.getProperty("os.name") + " " + 
+                                                       System.getProperty("os.arch")));
+                       connection.setRequestProperty("X-OpenRocket-Java", 
+                                       Communicator.encode(System.getProperty("java.vendor") + " " + 
+                                                       System.getProperty("java.version")));
+                       connection.setRequestProperty("X-OpenRocket-Country", 
+                                       Communicator.encode(System.getProperty("user.country") + " " +
+                                                       Communicator.encode(System.getProperty("user.timezone"))));
+                       
+                       InputStream is = null;
+                       try {
+                               connection.connect();
+                               
+                               if (connection.getResponseCode() == Communicator.UPDATE_INFO_NO_UPDATE_CODE) {
+                                       // No updates are available
+                                       info = new UpdateInfo();
+                                       return;
+                               }
+                               
+                               if (connection.getResponseCode() != Communicator.UPDATE_INFO_UPDATE_AVAILABLE) {
+                                       // Error communicating with server
+                                       return;
+                               }
+                               
+                               if (!Communicator.UPDATE_INFO_CONTENT_TYPE.equalsIgnoreCase(
+                                               connection.getContentType())) {
+                                       // Unknown response type
+                                       return;
+                               }
+                               
+                               
+                               // Update is available, parse input
+                               is = connection.getInputStream();
+                               is = new LimitedInputStream(is, Communicator.MAX_INPUT_BYTES);
+                               String encoding = connection.getContentEncoding();
+                               if (encoding == null || encoding.equals(""))
+                                       encoding = "UTF-8";
+                               BufferedReader reader = new BufferedReader(new InputStreamReader(is, encoding));
+                               
+                               String version = null;
+                               ArrayList<ComparablePair<Integer, String>> updates = 
+                                       new ArrayList<ComparablePair<Integer, String>>();
+                               
+                               String line = reader.readLine();
+                               while (line != null) {
+                                       
+                                       if (line.matches("^Version:[a-zA-Z0-9._ -]{1,30}$")) {
+                                               version = line.substring(8).trim();
+                                       } else if (line.matches("^[0-9]{1,9}:\\P{Cntrl}{1,300}$")) {
+                                               String[] split = line.split(":", 2);
+                                               int n = Integer.parseInt(split[0]);
+                                               updates.add(new ComparablePair<Integer,String>(n, split[1].trim()));
+                                       }
+                                       // Ignore line otherwise
+                                       line = reader.readLine();
+                               }
+                               
+                               // Check version input
+                               if (version == null || version.length() == 0 || 
+                                               version.equalsIgnoreCase(Prefs.getVersion())) {
+                                       // Invalid response
+                                       return;
+                               }
+                               
+                               
+                               info = new UpdateInfo(version, updates);
+                               
+                       } finally {
+                               try {
+                                       if (is != null)
+                                               is.close();
+                                       connection.disconnect();
+                               } catch (Exception e) {
+                                       e.printStackTrace();
+                               }
+                       }
+               }
+       }
+}
index f3fa4bcf90295114038d1b5eeec9d0037325555b..9ec9f589a09b17c93e7f304ce26370036ac7b231 100644 (file)
@@ -79,6 +79,7 @@ public class Databases {
                BULK_MATERIAL.add(new Material.Bulk("Cardboard",         680, false));
                BULK_MATERIAL.add(new Material.Bulk("Carbon fiber",     1780, false));
                BULK_MATERIAL.add(new Material.Bulk("Cork",                      240, false));
+               BULK_MATERIAL.add(new Material.Bulk("Depron",             40, false));
                BULK_MATERIAL.add(new Material.Bulk("Fiberglass",       1850, false));
                BULK_MATERIAL.add(new Material.Bulk("Kraft phenolic",950, false));
                BULK_MATERIAL.add(new Material.Bulk("Maple",             755, false));
@@ -89,6 +90,7 @@ 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));
+               // TODO: CRITICAL: Add styrofoam
                BULK_MATERIAL.add(new Material.Bulk("Quantum tubing",1050, false));
                
                SURFACE_MATERIAL.add(new Material.Surface("Ripstop nylon",                      0.067, false));
@@ -141,6 +143,14 @@ public class Databases {
        }
        
        
+       /*
+        * Used just for ensuring initialization of the class.
+        */
+       public static void fakeMethod() {
+               
+       }
+       
+       
        /**
         * Find a material from the database with the specified type and name.  Returns
         * <code>null</code> if the specified material could not be found.
diff --git a/src/net/sf/openrocket/gui/components/HtmlLabel.java b/src/net/sf/openrocket/gui/components/HtmlLabel.java
new file mode 100644 (file)
index 0000000..59fdbfa
--- /dev/null
@@ -0,0 +1,38 @@
+package net.sf.openrocket.gui.components;
+
+import java.awt.Dimension;
+
+import javax.swing.JLabel;
+
+/**
+ * A JLabel that limits the minimum and maximum height of the label to the
+ * initial preferred height of the label.  This is required in labels that use HTML
+ * since these often cause the panels to expand too much in height.
+ * 
+ * @author Sampo Niskanen <sampo.niskanen@iki.fi>
+ */
+public class HtmlLabel extends JLabel {
+
+       public HtmlLabel() {
+               super();
+               limitSize();
+       }
+
+       public HtmlLabel(String text) {
+               super(text);
+               limitSize();
+       }
+
+       public HtmlLabel(String text, int horizontalAlignment) {
+               super(text, horizontalAlignment);
+               limitSize();
+       }
+       
+       
+       private void limitSize() {
+               Dimension dim = this.getPreferredSize();
+               this.setMinimumSize(new Dimension(0, dim.height));
+               this.setMaximumSize(new Dimension(Integer.MAX_VALUE, dim.height));
+       }
+
+}
diff --git a/src/net/sf/openrocket/gui/components/ResizeLabel.java b/src/net/sf/openrocket/gui/components/ResizeLabel.java
deleted file mode 100644 (file)
index 0978252..0000000
+++ /dev/null
@@ -1,47 +0,0 @@
-package net.sf.openrocket.gui.components;
-
-import java.awt.Font;
-import javax.swing.JLabel;
-
-/**
- * A resizeable JLabel.  The method resizeFont(float) changes the current font size by the
- * given (positive or negative) amount.  The change is relative to the current font size.
- * <p>
- * A nice small text is achievable by  <code>new ResizeLabel("My text", -2);</code>
- * 
- * @author Sampo Niskanen <sampo.niskanen@iki.fi>
- */
-
-public class ResizeLabel extends JLabel {
-       
-       public ResizeLabel() {
-               super();
-       }
-       
-       public ResizeLabel(String text) {
-               super(text);
-       }
-       
-       public ResizeLabel(float size) {
-               super();
-               resizeFont(size);
-       }
-       
-       public ResizeLabel(String text, float size) {
-               super(text);
-               resizeFont(size);
-       }
-       
-       public ResizeLabel(String text, int horizontalAlignment, float size) {
-               super(text, horizontalAlignment);
-               resizeFont(size);
-       }
-       
-       
-       public void resizeFont(float size) {
-               Font font = this.getFont();
-               font = font.deriveFont(font.getSize2D()+size);
-               this.setFont(font);
-       }
-       
-}
diff --git a/src/net/sf/openrocket/gui/components/StyledLabel.java b/src/net/sf/openrocket/gui/components/StyledLabel.java
new file mode 100644 (file)
index 0000000..cad8a25
--- /dev/null
@@ -0,0 +1,111 @@
+package net.sf.openrocket.gui.components;
+
+import java.awt.Font;
+
+import javax.swing.JLabel;
+import javax.swing.SwingConstants;
+
+/**
+ * A resizeable and styleable JLabel.  The method {@link #resizeFont(float)} changes the 
+ * current font size by the given (positive or negative) amount.  The change is relative 
+ * to the current font size.  The method {@link #setFontStyle(Style)} sets the style
+ * (bold/italic) of the font.
+ * <p>
+ * A nice small text is achievable by  <code>new ResizeLabel("My text", -2);</code>
+ * 
+ * @author Sampo Niskanen <sampo.niskanen@iki.fi>
+ */
+
+public class StyledLabel extends JLabel {
+       
+       public enum Style {
+               PLAIN(Font.PLAIN),
+               BOLD(Font.BOLD),
+               ITALIC(Font.ITALIC),
+               BOLD_ITALIC(Font.BOLD | Font.ITALIC);
+               
+               private int style;
+               Style(int fontStyle) {
+                       this.style = fontStyle;
+               }
+               public int getFontStyle() {
+                       return style;
+               }
+       }
+       
+       
+       
+       public StyledLabel() {
+               this("", SwingConstants.LEADING, 0f);
+       }
+       
+       public StyledLabel(String text) {
+               this(text, SwingConstants.LEADING, 0f);
+       }
+       
+       public StyledLabel(float size) {
+               this("", SwingConstants.LEADING, size);
+       }
+       
+       public StyledLabel(String text, float size) {
+               this(text, SwingConstants.LEADING, size);
+       }
+       
+       public StyledLabel(String text, int horizontalAlignment, float size) {
+               super(text, horizontalAlignment);
+               resizeFont(size);
+               checkPreferredSize(size, Style.PLAIN);
+       }
+       
+       
+
+       public StyledLabel(Style style) {
+               this("", SwingConstants.LEADING, 0f, style);
+       }
+       
+       public StyledLabel(String text, Style style) {
+               this(text, SwingConstants.LEADING, 0f, style);
+       }
+       
+       public StyledLabel(float size, Style style) {
+               this("", SwingConstants.LEADING, size, style);
+       }
+       
+       public StyledLabel(String text, float size, Style style) {
+               this(text, SwingConstants.LEADING, size, style);
+       }
+       
+       public StyledLabel(String text, int horizontalAlignment, float size, Style style) {
+               super(text, horizontalAlignment);
+               resizeFont(size);
+               setFontStyle(style);
+               checkPreferredSize(size, style);
+       }
+       
+       
+       
+       
+       private void checkPreferredSize(float size, Style style) {
+               String str = this.getText();
+               if (str.startsWith("<html>") && str.indexOf("<br") < 0) {
+                       StyledLabel label = new StyledLabel("plaintext", size, style);
+                       label.validate();
+                       System.out.println("Plain-text label: " + label.getPreferredSize());
+                       System.out.println("HTML label: " + this.getPreferredSize());
+               }
+       }
+       
+       
+       
+       public void resizeFont(float size) {
+               Font font = this.getFont();
+               font = font.deriveFont(font.getSize2D()+size);
+               this.setFont(font);
+       }
+       
+       public void setFontStyle(Style style) {
+               Font font = this.getFont();
+               font = font.deriveFont(style.getFontStyle());
+               this.setFont(font);
+       }
+}
index 8a81a9adb5114ef7ca3227c371996f2cc2362343..498d5f8d83751cb25a27a4d7df26c258f1d0816e 100644 (file)
@@ -36,7 +36,7 @@ import net.sf.openrocket.unit.UnitGroup;
  * @author Sampo Niskanen <sampo.niskanen@iki.fi>
  */
 
-public class UnitSelector extends ResizeLabel implements ChangeListener, MouseListener,
+public class UnitSelector extends StyledLabel implements ChangeListener, MouseListener,
                ItemSelectable {
 
        private DoubleModel model;
index efec1267965f97a6aa2ffaf6d8f10a9c88dc07c9..df5ce634fa42f71816e0188a57d9d489ac45e04e 100644 (file)
@@ -15,7 +15,9 @@ import net.sf.openrocket.gui.SpinnerEditor;
 import net.sf.openrocket.gui.adaptors.DoubleModel;
 import net.sf.openrocket.gui.adaptors.EnumModel;
 import net.sf.openrocket.gui.components.BasicSlider;
+import net.sf.openrocket.gui.components.StyledLabel;
 import net.sf.openrocket.gui.components.UnitSelector;
+import net.sf.openrocket.gui.components.StyledLabel.Style;
 import net.sf.openrocket.rocketcomponent.FinSet;
 import net.sf.openrocket.rocketcomponent.FreeformFinSet;
 import net.sf.openrocket.rocketcomponent.RocketComponent;
@@ -107,7 +109,8 @@ public abstract class FinSetConfig extends RocketComponentConfig {
 //             JPanel panel = new JPanel(new MigLayout("fillx, align 20% 20%, gap rel unrel",
 //                             "[40lp][80lp::][30lp::][100lp::]",""));
 
-               panel.add(new JLabel("<html><b>Through-the-wall fin tabs:</b>"), "spanx, wrap 30lp");
+               panel.add(new StyledLabel("Through-the-wall fin tabs:", Style.BOLD), 
+                               "spanx, wrap 30lp");
                
                JLabel label;
                DoubleModel m;
index 18b18032aee09a0f0f619f19c24e7b762ac25296..52e02686afb00ae360f7e2d9d8e9e008d4bbb967 100644 (file)
@@ -22,7 +22,7 @@ import net.sf.openrocket.gui.adaptors.DoubleModel;
 import net.sf.openrocket.gui.adaptors.EnumModel;
 import net.sf.openrocket.gui.adaptors.IntegerModel;
 import net.sf.openrocket.gui.components.BasicSlider;
-import net.sf.openrocket.gui.components.ResizeLabel;
+import net.sf.openrocket.gui.components.StyledLabel;
 import net.sf.openrocket.gui.components.UnitSelector;
 import net.sf.openrocket.gui.scalefigure.FinPointFigure;
 import net.sf.openrocket.gui.scalefigure.ScaleScrollPane;
@@ -212,14 +212,14 @@ public class FreeformFinSetConfig extends FinSetConfig {
                panel.add(tablePane,"growy, width 100lp:100lp:, height 100lp:250lp:");
                panel.add(figurePane,"gap unrel, spanx, growx, growy 1000, height 100lp:250lp:, wrap");
                
-               panel.add(new ResizeLabel("Double-click", -2), "alignx 50%");
+               panel.add(new StyledLabel("Double-click", -2), "alignx 50%");
                
                panel.add(new ScaleSelector(figurePane),"spany 2");
-               panel.add(new ResizeLabel("Click+drag: Add and move points   " +
+               panel.add(new StyledLabel("Click+drag: Add and move points   " +
                                "Ctrl+click: Remove point", -2), "spany 2, right, wrap");
                
                
-               panel.add(new ResizeLabel("to edit", -2), "alignx 50%");
+               panel.add(new StyledLabel("to edit", -2), "alignx 50%");
                
                return panel;
        }
index f41ca5ce2c0733db0b0a5edccf00e7ec6586f3e1..d3b37bcbe3e7138a2c1d716cf152d4311a865463 100644 (file)
@@ -17,7 +17,10 @@ import net.sf.openrocket.gui.adaptors.EnumModel;
 import net.sf.openrocket.gui.adaptors.IntegerModel;
 import net.sf.openrocket.gui.adaptors.MaterialModel;
 import net.sf.openrocket.gui.components.BasicSlider;
+import net.sf.openrocket.gui.components.HtmlLabel;
+import net.sf.openrocket.gui.components.StyledLabel;
 import net.sf.openrocket.gui.components.UnitSelector;
+import net.sf.openrocket.gui.components.StyledLabel.Style;
 import net.sf.openrocket.material.Material;
 import net.sf.openrocket.rocketcomponent.MassComponent;
 import net.sf.openrocket.rocketcomponent.Parachute;
@@ -36,7 +39,7 @@ public class ParachuteConfig extends RecoveryDeviceConfig {
                
                
                //// Canopy
-               panel.add(new JLabel("<html><b>Canopy:</b>"), "wrap unrel");
+               panel.add(new StyledLabel("Canopy:", Style.BOLD), "wrap unrel");
                
 
                panel.add(new JLabel("Diameter:"));
@@ -62,7 +65,7 @@ public class ParachuteConfig extends RecoveryDeviceConfig {
                
                
                // CD
-               JLabel label = new JLabel("<html>Drag coefficient C<sub>D</sub>:");
+               JLabel label = new HtmlLabel("<html>Drag coefficient C<sub>D</sub>:");
                String tip = "<html>The drag coefficient relative to the total area of the parachute.<br>" +
                                "A larger drag coefficient yields a slowed descent rate.  " +
                                "A typical value for parachutes is 0.8.";
@@ -89,7 +92,7 @@ public class ParachuteConfig extends RecoveryDeviceConfig {
                
                
                ////  Shroud lines
-               panel.add(new JLabel("<html><b>Shroud lines:</b>"), "wrap unrel");
+               panel.add(new StyledLabel("Shroud lines:", Style.BOLD), "wrap unrel");
 
 
                panel.add(new JLabel("Number of lines:"));
index 02011b71f37a01f145e26a72c186c67e7386153d..5f94bbb9db1e8a1cc6a4a40ac1ba7d49533033f1 100644 (file)
@@ -17,7 +17,8 @@ import net.sf.openrocket.gui.adaptors.DoubleModel;
 import net.sf.openrocket.gui.adaptors.EnumModel;
 import net.sf.openrocket.gui.adaptors.MaterialModel;
 import net.sf.openrocket.gui.components.BasicSlider;
-import net.sf.openrocket.gui.components.ResizeLabel;
+import net.sf.openrocket.gui.components.HtmlLabel;
+import net.sf.openrocket.gui.components.StyledLabel;
 import net.sf.openrocket.gui.components.UnitSelector;
 import net.sf.openrocket.material.Material;
 import net.sf.openrocket.rocketcomponent.MassComponent;
@@ -93,7 +94,7 @@ public class StreamerConfig extends RecoveryDeviceConfig {
                
                
                // CD
-               JLabel label = new JLabel("<html>Drag coefficient C<sub>D</sub>:");
+               JLabel label = new HtmlLabel("<html>Drag coefficient C<sub>D</sub>:");
                String tip = "<html>The drag coefficient relative to the total area of the streamer.<br>" +
                                "A larger drag coefficient yields a slowed descent rate.";
                label.setToolTipText(tip);
@@ -110,7 +111,7 @@ public class StreamerConfig extends RecoveryDeviceConfig {
                check.setText("Automatic");
                panel.add(check,"skip, span, wrap");
                
-               panel.add(new ResizeLabel("The drag coefficient is relative to the area of the streamer.",
+               panel.add(new StyledLabel("The drag coefficient is relative to the area of the streamer.",
                                -2), "span, wrap");
                
                
index 516be5ec3012b762647f4e3670e6d8957a51ab57..41014f49d858ab1f0665fecab2134a4e1834cbae 100644 (file)
@@ -10,7 +10,7 @@ import javax.swing.JLabel;
 import javax.swing.JPanel;
 
 import net.miginfocom.swing.MigLayout;
-import net.sf.openrocket.gui.components.ResizeLabel;
+import net.sf.openrocket.gui.components.StyledLabel;
 import net.sf.openrocket.gui.components.URLLabel;
 import net.sf.openrocket.util.GUIUtil;
 import net.sf.openrocket.util.Icons;
@@ -31,18 +31,18 @@ public class AboutDialog extends JDialog {
                panel.add(new JLabel(Icons.loadImageIcon("pix/icon/icon-about.png", "OpenRocket")), 
                                "spany 5, top");
                
-               panel.add(new ResizeLabel("OpenRocket", 20), "ax 50%, growy, wrap para");
-               panel.add(new ResizeLabel("Version " + version, 3), "ax 50%, growy, wrap rel");
+               panel.add(new StyledLabel("OpenRocket", 20), "ax 50%, growy, wrap para");
+               panel.add(new StyledLabel("Version " + version, 3), "ax 50%, growy, wrap rel");
                
                String source = Prefs.getBuildSource();
                if (!Prefs.DEFAULT_BUILD_SOURCE.equalsIgnoreCase(source)) {
-                       panel.add(new ResizeLabel("Distributed by " + source, -1), 
+                       panel.add(new StyledLabel("Distributed by " + source, -1), 
                                        "ax 50%, growy, wrap para");
                } else {
-                       panel.add(new ResizeLabel(" ", -1), "ax 50%, growy, wrap para");
+                       panel.add(new StyledLabel(" ", -1), "ax 50%, growy, wrap para");
                }
                
-               panel.add(new ResizeLabel("Copyright \u00A9 2007-2009 Sampo Niskanen"), 
+               panel.add(new StyledLabel("Copyright \u00A9 2007-2009 Sampo Niskanen"), 
                                "ax 50%, growy, wrap para");
                
                panel.add(new URLLabel(OPENROCKET_URL), "ax 50%, growy, wrap para");
index 6df6803148be506d544f9a3c51558f3ced382936..9ebd9f2a8f640bdda9e539db33e7885cfe762ae6 100644 (file)
@@ -25,8 +25,8 @@ import javax.swing.JScrollPane;
 import javax.swing.JTextArea;
 
 import net.miginfocom.swing.MigLayout;
-import net.sf.openrocket.communication.Communication;
-import net.sf.openrocket.gui.components.ResizeLabel;
+import net.sf.openrocket.communication.BugReporter;
+import net.sf.openrocket.gui.components.StyledLabel;
 import net.sf.openrocket.gui.components.SelectableLabel;
 import net.sf.openrocket.util.GUIUtil;
 import net.sf.openrocket.util.JarUtil;
@@ -66,7 +66,7 @@ public class BugReportDialog extends JDialog {
                panel.add(new JScrollPane(textArea), "grow, wrap");
                
                
-               panel.add(new ResizeLabel("The information above may be included in a public " +
+               panel.add(new StyledLabel("The information above may be included in a public " +
                                "bug report.  Make sure it does not contain any sensitive information you " +
                                "do not want to be made public.", -1), "wrap para");
                
@@ -107,7 +107,7 @@ public class BugReportDialog extends JDialog {
                                String text = textArea.getText();
                                try {
                                        
-                                       Communication.sendBugReport(text);
+                                       BugReporter.sendBugReport(text);
 
                                        // Success if we came here
                                        JOptionPane.showMessageDialog(BugReportDialog.this,
index 8157744344b55763e15868e0aa697546cf182ebe..cd7d9c98b757cc42ad3674940864d3249dabb7d1 100644 (file)
@@ -45,7 +45,7 @@ import net.sf.openrocket.gui.adaptors.ColumnTableModel;
 import net.sf.openrocket.gui.adaptors.DoubleModel;
 import net.sf.openrocket.gui.adaptors.MotorConfigurationModel;
 import net.sf.openrocket.gui.components.BasicSlider;
-import net.sf.openrocket.gui.components.ResizeLabel;
+import net.sf.openrocket.gui.components.StyledLabel;
 import net.sf.openrocket.gui.components.StageSelector;
 import net.sf.openrocket.gui.components.UnitSelector;
 import net.sf.openrocket.gui.scalefigure.RocketPanel;
@@ -370,14 +370,14 @@ public class ComponentAnalysisDialog extends JDialog implements ChangeListener {
                });
                
 
-               panel.add(new ResizeLabel("Reference length: ", -1), 
+               panel.add(new StyledLabel("Reference length: ", -1), 
                                "span, split, gapleft para, gapright rel");
                DoubleModel dm = new DoubleModel(conditions, "RefLength", UnitGroup.UNITS_LENGTH);
                UnitSelector sel = new UnitSelector(dm, true);
                sel.resizeFont(-1);
                panel.add(sel, "gapright para");
                
-               panel.add(new ResizeLabel("Reference area: ", -1), "gapright rel");
+               panel.add(new StyledLabel("Reference area: ", -1), "gapright rel");
                dm = new DoubleModel(conditions, "RefArea", UnitGroup.UNITS_AREA);
                sel = new UnitSelector(dm, true);
                sel.resizeFont(-1);
index 7fb7ad4001748bd16d64fb93e6f42d54bbbabdd9..ef22d84509724d5ccfdf42b104da02a40dfda1d2 100644 (file)
@@ -16,7 +16,7 @@ import javax.swing.JTextField;
 
 import net.miginfocom.swing.MigLayout;
 import net.sf.openrocket.gui.adaptors.DoubleModel;
-import net.sf.openrocket.gui.components.ResizeLabel;
+import net.sf.openrocket.gui.components.StyledLabel;
 import net.sf.openrocket.gui.components.UnitSelector;
 import net.sf.openrocket.material.Material;
 import net.sf.openrocket.util.GUIUtil;
@@ -54,7 +54,7 @@ public class CustomMaterialDialog extends JDialog {
                                        "gapleft para, span, wrap" + (note == null ? " para":""));
                }
                if (note != null) {
-                       panel.add(new ResizeLabel(note, -1), "span, wrap para");
+                       panel.add(new StyledLabel(note, -1), "span, wrap para");
                }
                
 
index b58113da597ae4a89ebc159955ead6ca63d5e4b6..10759a69f3e7e62cde008ed600857a5c8fd47bd0 100644 (file)
@@ -14,7 +14,7 @@ import javax.swing.JScrollPane;
 import javax.swing.JTextArea;
 
 import net.miginfocom.swing.MigLayout;
-import net.sf.openrocket.gui.components.ResizeLabel;
+import net.sf.openrocket.gui.components.StyledLabel;
 import net.sf.openrocket.util.GUIUtil;
 
 public class LicenseDialog extends JDialog {
@@ -32,7 +32,7 @@ public class LicenseDialog extends JDialog {
                
                JPanel panel = new JPanel(new MigLayout("fill"));
                
-               panel.add(new ResizeLabel("OpenRocket license", 10), "ax 50%, wrap para");
+               panel.add(new StyledLabel("OpenRocket license", 10), "ax 50%, wrap para");
 
                String licenseText;
                try {
index 6893dccf596bad062a6c3c0324e738880f892956..887085df94145b341cb83f26e9484fa872ddc494 100644 (file)
@@ -37,7 +37,7 @@ import javax.swing.table.TableRowSorter;
 
 import net.miginfocom.swing.MigLayout;
 import net.sf.openrocket.database.Databases;
-import net.sf.openrocket.gui.components.ResizeLabel;
+import net.sf.openrocket.gui.components.StyledLabel;
 import net.sf.openrocket.motor.Motor;
 import net.sf.openrocket.unit.UnitGroup;
 import net.sf.openrocket.util.GUIUtil;
@@ -251,7 +251,7 @@ public class MotorChooserDialog extends JDialog {
                        }
                });
                panel.add(delayBox,"gapright unrel");
-               panel.add(new ResizeLabel("(Number of seconds or \"None\")", -1), "wrap para");
+               panel.add(new StyledLabel("(Number of seconds or \"None\")", -1), "wrap para");
                setDelays(false);
                
                
diff --git a/src/net/sf/openrocket/gui/dialogs/UpdateInfoDialog.java b/src/net/sf/openrocket/gui/dialogs/UpdateInfoDialog.java
new file mode 100644 (file)
index 0000000..518403c
--- /dev/null
@@ -0,0 +1,73 @@
+package net.sf.openrocket.gui.dialogs;
+
+import java.awt.Window;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.util.Collections;
+import java.util.List;
+
+import javax.swing.JButton;
+import javax.swing.JDialog;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+
+import net.miginfocom.swing.MigLayout;
+import net.sf.openrocket.communication.UpdateInfo;
+import net.sf.openrocket.gui.components.URLLabel;
+import net.sf.openrocket.util.ComparablePair;
+import net.sf.openrocket.util.GUIUtil;
+import net.sf.openrocket.util.Icons;
+
+public class UpdateInfoDialog extends JDialog {
+
+       public UpdateInfoDialog(UpdateInfo info) {
+               super((Window)null, "OpenRocket update available", ModalityType.APPLICATION_MODAL);
+               
+               JPanel panel = new JPanel(new MigLayout("fill"));
+               
+
+               panel.add(new JLabel(Icons.loadImageIcon("pix/icon/icon-about.png", "OpenRocket")), 
+                               "spany 100, top");
+               
+               
+               panel.add(new JLabel("<html><b>OpenRocket version " + info.getLatestVersion() +
+                               " is available!"), "wrap para");
+               
+               List<ComparablePair<Integer, String>> updates = info.getUpdates();
+               if (updates.size() > 0) {
+                       panel.add(new JLabel("Updates include:"), "wrap rel");
+                       
+                       Collections.sort(updates);
+                       int count = 0;
+                       int n = -1;
+                       for (int i=updates.size()-1; i>=0; i--) {
+                               // Add only specific number of top features
+                               if (count >= 4 && n != updates.get(i).getU())
+                                       break;
+                               n = updates.get(i).getU();
+                               panel.add(new JLabel("   \u2022 " + updates.get(i).getV()), "wrap 0px");
+                               count++;
+                       }
+               }
+
+               panel.add(new JLabel("Download the new version from:"), 
+                               "gaptop para, alignx 50%, wrap unrel");
+               panel.add(new URLLabel(AboutDialog.OPENROCKET_URL), "alignx 50%, wrap para");
+               
+               JButton button = new JButton("Close");
+               button.addActionListener(new ActionListener() {
+                       @Override
+                       public void actionPerformed(ActionEvent e) {
+                               UpdateInfoDialog.this.dispose();
+                       }
+               });
+               panel.add(button, "right");
+               
+               this.add(panel);
+               
+               this.pack();
+               this.setLocationRelativeTo(null);
+               GUIUtil.setDisposableDialogOptions(this, button);
+       }
+       
+}
index 2ef8e113edc2adb02cbf89749dbe1dd74c7a4160..38cc36fe7b081bd6516328728fef186b265462a1 100644 (file)
@@ -19,7 +19,7 @@ import javax.swing.JPanel;
 import javax.swing.JTabbedPane;
 
 import net.miginfocom.swing.MigLayout;
-import net.sf.openrocket.gui.components.ResizeLabel;
+import net.sf.openrocket.gui.components.StyledLabel;
 import net.sf.openrocket.unit.Unit;
 import net.sf.openrocket.unit.UnitGroup;
 import net.sf.openrocket.util.GUIUtil;
@@ -204,7 +204,7 @@ public class PreferencesDialog extends JDialog {
                panel.add(button, "grow, wrap para");
                
                
-               panel.add(new ResizeLabel("The effects will take place the next time you open a window.",-2),
+               panel.add(new StyledLabel("The effects will take place the next time you open a window.",-2),
                                "spanx, wrap");
                
 
index 8ad7601ad7692f52af35d24095e1e31413e0564a..fcf74ca29abff7bcad5304e826090265db1e2047 100644 (file)
@@ -48,6 +48,7 @@ import javax.swing.ListSelectionModel;
 import javax.swing.LookAndFeel;
 import javax.swing.ScrollPaneConstants;
 import javax.swing.SwingUtilities;
+import javax.swing.Timer;
 import javax.swing.ToolTipManager;
 import javax.swing.UIManager;
 import javax.swing.border.TitledBorder;
@@ -60,6 +61,9 @@ import javax.swing.tree.TreeSelectionModel;
 
 import net.miginfocom.swing.MigLayout;
 import net.sf.openrocket.aerodynamics.WarningSet;
+import net.sf.openrocket.communication.UpdateInfo;
+import net.sf.openrocket.communication.UpdateInfoRetriever;
+import net.sf.openrocket.database.Databases;
 import net.sf.openrocket.document.OpenRocketDocument;
 import net.sf.openrocket.file.GeneralRocketLoader;
 import net.sf.openrocket.file.OpenRocketSaver;
@@ -74,6 +78,7 @@ import net.sf.openrocket.gui.dialogs.ComponentAnalysisDialog;
 import net.sf.openrocket.gui.dialogs.ExampleDesignDialog;
 import net.sf.openrocket.gui.dialogs.LicenseDialog;
 import net.sf.openrocket.gui.dialogs.SwingWorkerDialog;
+import net.sf.openrocket.gui.dialogs.UpdateInfoDialog;
 import net.sf.openrocket.gui.dialogs.WarningDialog;
 import net.sf.openrocket.gui.dialogs.preferences.PreferencesDialog;
 import net.sf.openrocket.gui.scalefigure.RocketPanel;
@@ -1151,7 +1156,18 @@ public class BasicFrame extends JFrame {
        
        
        private static void runMain(String[] args) {
-
+               
+               // Start update info fetching
+               final UpdateInfoRetriever updateInfo;
+               if (Prefs.getCheckUpdates()) {
+                       updateInfo = new UpdateInfoRetriever();
+                       updateInfo.start();
+               } else {
+                       updateInfo = null;
+               }
+               
+               
+               
                /*
                 * Set the look-and-feel.  On Linux, Motif/Metal is sometimes incorrectly used 
                 * which is butt-ugly, so if the system l&f is Motif/Metal, we search for a few
@@ -1200,12 +1216,51 @@ public class BasicFrame extends JFrame {
                
                // Load defaults
                Prefs.loadDefaultUnits();
+               
 
+               // Load motors etc.
+               Databases.fakeMethod();
                
-               // Starting action
+               // Starting action (load files or open new document)
                if (!handleCommandLine(args)) {
                        newAction();
                }
+               
+               
+               // Check whether update info has been fetched or whether it needs more time
+               checkUpdateStatus(updateInfo);
+       }
+       
+       
+       private static void checkUpdateStatus(final UpdateInfoRetriever updateInfo) {
+               if (updateInfo == null)
+                       return;
+
+               int delay = 1000;
+               if (!updateInfo.isRunning())
+                       delay = 100;
+
+               final Timer timer = new Timer(delay, null);
+
+               ActionListener listener = new ActionListener() {
+                       private int count = 5;
+                       @Override
+                       public void actionPerformed(ActionEvent e) {
+                               if (!updateInfo.isRunning()) {
+                                       timer.stop();
+
+                                       UpdateInfo info = updateInfo.getUpdateInfo();
+                                       if (info != null && !Prefs.getVersion().equals(info.getLatestVersion())) {
+                                               new UpdateInfoDialog(info).setVisible(true);
+                                       }
+                               }
+                               count--;
+                               if (count <= 0)
+                                       timer.stop();
+                       }
+               };
+               timer.addActionListener(listener);
+               timer.start();
        }
        
        
index f140225669beb7dd687faeb435d61cacecf8e91f..a8072de47e72be4d0ac68b6dbd28b75d7e2d3be1 100644 (file)
@@ -26,7 +26,7 @@ import javax.swing.tree.TreeSelectionModel;
 
 import net.miginfocom.swing.MigLayout;
 import net.sf.openrocket.document.OpenRocketDocument;
-import net.sf.openrocket.gui.components.ResizeLabel;
+import net.sf.openrocket.gui.components.StyledLabel;
 import net.sf.openrocket.gui.configdialog.ComponentConfigDialog;
 import net.sf.openrocket.rocketcomponent.BodyComponent;
 import net.sf.openrocket.rocketcomponent.BodyTube;
@@ -269,7 +269,7 @@ public class ComponentAddButtons extends JPanel implements Scrollable {
                        // Add labels
                        String[] l = text.split("\n");
                        for (int i=0; i<l.length; i++) {
-                               add(new ResizeLabel(l[i],SwingConstants.CENTER,-3.0f),"growx");
+                               add(new StyledLabel(l[i],SwingConstants.CENTER,-3.0f),"growx");
                        }
                        
                        add(new JLabel(),"push, sizegroup spacing");
@@ -502,7 +502,7 @@ public class ComponentAddButtons extends JPanel implements Scrollable {
                        JPanel panel = new JPanel(new MigLayout());
                        JCheckBox check = new JCheckBox("Do not ask me again");
                        panel.add(check,"wrap");
-                       panel.add(new ResizeLabel("You can change the default operation in the " +
+                       panel.add(new StyledLabel("You can change the default operation in the " +
                                        "preferences.",-2));
                        
                        int sel = JOptionPane.showOptionDialog(null,  // parent component 
index 5958e18c2b293babfb65e53ac1482fe2a02d48ce..76f3c633b4fc3163f5e27f467ae72e521e4d8cf0 100644 (file)
@@ -15,7 +15,7 @@ import javax.swing.KeyStroke;
 import net.miginfocom.swing.MigLayout;
 import net.sf.openrocket.document.OpenRocketDocument;
 import net.sf.openrocket.document.Simulation;
-import net.sf.openrocket.gui.components.ResizeLabel;
+import net.sf.openrocket.gui.components.StyledLabel;
 import net.sf.openrocket.gui.configdialog.ComponentConfigDialog;
 import net.sf.openrocket.rocketcomponent.ComponentChangeEvent;
 import net.sf.openrocket.rocketcomponent.ComponentChangeListener;
@@ -209,7 +209,7 @@ public class RocketActions {
                        JPanel panel = new JPanel(new MigLayout());
                        JCheckBox dontAsk = new JCheckBox("Do not ask me again");
                        panel.add(dontAsk,"wrap");
-                       panel.add(new ResizeLabel("You can change the default operation in the " +
+                       panel.add(new StyledLabel("You can change the default operation in the " +
                                        "preferences.",-2));
 
                        int ret = JOptionPane.showConfirmDialog(
index 784ee09a09e3fb27b814d8a540f56acc1eee6c21..e62c2aa6ce9878a060ffd784fb4008d6bc9b145a 100644 (file)
@@ -33,7 +33,7 @@ import net.sf.openrocket.document.events.DocumentChangeListener;
 import net.sf.openrocket.document.events.SimulationChangeEvent;
 import net.sf.openrocket.gui.adaptors.Column;
 import net.sf.openrocket.gui.adaptors.ColumnTableModel;
-import net.sf.openrocket.gui.components.ResizeLabel;
+import net.sf.openrocket.gui.components.StyledLabel;
 import net.sf.openrocket.rocketcomponent.ComponentChangeEvent;
 import net.sf.openrocket.rocketcomponent.ComponentChangeListener;
 import net.sf.openrocket.simulation.FlightData;
@@ -146,7 +146,7 @@ public class SimulationPanel extends JPanel {
                                                JPanel panel = new JPanel(new MigLayout());
                                                JCheckBox dontAsk = new JCheckBox("Do not ask me again");
                                                panel.add(dontAsk,"wrap");
-                                               panel.add(new ResizeLabel("You can change the default operation in the " +
+                                               panel.add(new StyledLabel("You can change the default operation in the " +
                                                                "preferences.",-2));
                                                
                                           int ret = JOptionPane.showConfirmDialog(SimulationPanel.this,
@@ -215,7 +215,7 @@ public class SimulationPanel extends JPanel {
                                                
                                                // Initialize the label
                                                if (label == null) {
-                                                       label = new ResizeLabel(2f);
+                                                       label = new StyledLabel(2f);
                                                        label.setIconTextGap(1);
 //                                                     label.setFont(label.getFont().deriveFont(Font.BOLD));
                                                }
index c402fa692f2ec4167a27ba7b33fd1828241c8d94..a2cbea7c82680740c1bfc1823258bb1002fcaeff 100644 (file)
@@ -21,7 +21,7 @@ import javax.swing.table.TableColumnModel;
 
 import net.miginfocom.swing.MigLayout;
 import net.sf.openrocket.document.Simulation;
-import net.sf.openrocket.gui.components.ResizeLabel;
+import net.sf.openrocket.gui.components.StyledLabel;
 import net.sf.openrocket.gui.components.UnitSelector;
 import net.sf.openrocket.simulation.FlightDataBranch;
 import net.sf.openrocket.simulation.FlightEvent;
@@ -160,7 +160,7 @@ public class PlotPanel extends JPanel {
                this.add(domainUnitSelector, "width 40lp, gapright para");
                
                
-               ResizeLabel desc = new ResizeLabel("<html><p>The data will be plotted in time order " +
+               StyledLabel desc = new StyledLabel("<html><p>The data will be plotted in time order " +
                                "even if the X axis type is not time.", -2);
                this.add(desc, "width :0px:, growx, wrap para");
                
diff --git a/src/net/sf/openrocket/util/LimitedInputStream.java b/src/net/sf/openrocket/util/LimitedInputStream.java
new file mode 100644 (file)
index 0000000..4e264fa
--- /dev/null
@@ -0,0 +1,83 @@
+package net.sf.openrocket.util;
+
+import java.io.FilterInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * A filtering InputStream that limits the number of bytes that can be
+ * read from a stream.   This can be used to enforce security, so that overlong
+ * input is ignored.
+ * 
+ * @author Sampo Niskanen <sampo.niskanen@iki.fi>
+ */
+public class LimitedInputStream extends FilterInputStream {
+
+       private int remaining;
+       
+       public LimitedInputStream(InputStream is, int limit) {
+               super(is);
+               this.remaining = limit;
+       }
+
+       
+       @Override
+       public int available() throws IOException {
+               int available = super.available();
+               return Math.min(available, remaining);
+       }
+
+
+       @Override
+       public int read(byte[] b, int off, int len) throws IOException {
+               if (remaining <= 0)
+                       return -1;
+               
+               int result = super.read(b, off, Math.min(len, remaining));
+               if (result >= 0)
+                       remaining -= result;
+               return result;
+       }
+
+
+       @Override
+       public long skip(long n) throws IOException {
+               if (n > remaining)
+                       n = remaining;
+               long result = super.skip(n);
+               remaining -= result;
+               return result;
+       }
+
+
+       @Override
+       public int read() throws IOException {
+               if (remaining <= 0)
+                       return -1;
+               
+               int result = super.read();
+               if (result >= 0)
+                       remaining--;
+               return result;
+       }
+
+       
+       
+       //  Disable mark support
+
+       @Override
+       public void mark(int readlimit) {
+
+       }
+
+       @Override
+       public boolean markSupported() {
+               return false;
+       }
+
+       @Override
+       public synchronized void reset() throws IOException {
+               throw new IOException("mark/reset not supported");
+       }
+       
+}
index 2b7c4101ddfe1fc4a71d3af6b5da4a26f2733347..f4f26133d81beb2d3fe35b4e291ae04a6746673b 100644 (file)
@@ -57,6 +57,7 @@ public class Prefs {
        private static final String BUILD_VERSION;
        private static final String BUILD_SOURCE;
        public static final String DEFAULT_BUILD_SOURCE = "default";
+       private static final boolean DEFAULT_CHECK_UPDATES;
        
        static {
                try {
@@ -81,6 +82,12 @@ public class Prefs {
                        
                        BUILD_SOURCE = props.getProperty("build.source");
                        
+                       String value = props.getProperty("build.checkupdates");
+                       if (value != null)
+                               DEFAULT_CHECK_UPDATES = Boolean.parseBoolean(value);
+                       else
+                               DEFAULT_CHECK_UPDATES = true;
+                       
                } catch (IOException e) {
                        throw new MissingResourceException(
                                        "Error reading build.properties",
@@ -103,6 +110,8 @@ public class Prefs {
        
        public static final String PLOT_SHOW_POINTS = "ShowPlotPoints";
        
+       private static final String CHECK_UPDATES = "CheckUpdates";
+       
        /**
         * Node to this application's preferences.
         * @deprecated  Use the static methods instead.
@@ -152,14 +161,18 @@ public class Prefs {
        }
        
        
-       private static final Material DEFAULT_LINE_MATERIAL = 
-               Databases.findMaterial(Material.Type.LINE, "Elastic cord (round 2mm, 1/16 in)", 
-                               0.0018, false);
-       private static final Material DEFAULT_SURFACE_MATERIAL = 
-               Databases.findMaterial(Material.Type.SURFACE, "Ripstop nylon", 0.067, false);
-       private static final Material DEFAULT_BULK_MATERIAL = 
-               Databases.findMaterial(Material.Type.BULK, "Cardboard", 680, false);
-
+       /*
+        * Within a holder class so they will load only when needed.
+        */
+       private static class DefaultMaterialHolder {
+               private static final Material DEFAULT_LINE_MATERIAL = 
+                       Databases.findMaterial(Material.Type.LINE, "Elastic cord (round 2mm, 1/16 in)", 
+                                       0.0018, false);
+               private static final Material DEFAULT_SURFACE_MATERIAL = 
+                       Databases.findMaterial(Material.Type.SURFACE, "Ripstop nylon", 0.067, false);
+               private static final Material DEFAULT_BULK_MATERIAL = 
+                       Databases.findMaterial(Material.Type.BULK, "Cardboard", 680, false);
+       }
        
        //////////////////////
        
@@ -242,6 +255,16 @@ public class Prefs {
 
        
        
+       public static boolean getCheckUpdates() {
+               return PREFNODE.getBoolean(CHECK_UPDATES, DEFAULT_CHECK_UPDATES);
+       }
+       
+       public static void setCheckUpdates(boolean check) {
+               PREFNODE.putBoolean(CHECK_UPDATES, check);
+               storeVersion();
+       }
+       
+       
        //////////////////
        
        public static File getDefaultDirectory() {
@@ -360,11 +383,11 @@ public class Prefs {
                
                switch (type) {
                case LINE:
-                       return DEFAULT_LINE_MATERIAL;
+                       return DefaultMaterialHolder.DEFAULT_LINE_MATERIAL;
                case SURFACE:
-                       return DEFAULT_SURFACE_MATERIAL;
+                       return DefaultMaterialHolder.DEFAULT_SURFACE_MATERIAL;
                case BULK:
-                       return DEFAULT_BULK_MATERIAL;
+                       return DefaultMaterialHolder.DEFAULT_BULK_MATERIAL;
                }
                throw new IllegalArgumentException("Unknown material type: "+type);
        }
index 28894cac01fa32b7ae2ec2562039621fd28ca156..356014faf98e99ab26013575ef2f869b1d2bc7de 100644 (file)
@@ -14,17 +14,24 @@ import net.sf.openrocket.motor.ThrustCurveMotor;
 
 public class MotorCompare {
        
+       /** Maximum allowed difference in maximum thrust */
        private static final double MAX_THRUST_MARGIN = 0.20;
+       /** Maximum allowed difference in total impulse */
        private static final double TOTAL_IMPULSE_MARGIN = 0.10;
+       /** Maximum allowed difference in mass values */
        private static final double MASS_MARGIN = 0.10;
-       
-       private static final double THRUST_MARGIN = 0.15;
-       
+
+       /** Number of time points in thrust curve to compare */
        private static final int DIVISIONS = 100;
+       /** Maximum difference in thrust for a time point to be considered invalid */
+       private static final double THRUST_MARGIN = 0.15;
+       /** Number of invalid time points allowed */
        private static final int ALLOWED_INVALID_POINTS = 15;
-       
+
+       /** Minimum number of thrust curve points allowed (incl. start and end points) */
        private static final int MIN_POINTS = 7;
 
+       
        public static void main(String[] args) throws IOException {
                final double maxThrust;
                final double maxTime;
diff --git a/test/net/sf/openrocket/communication/BugReportTest.java b/test/net/sf/openrocket/communication/BugReportTest.java
new file mode 100644 (file)
index 0000000..991b8fd
--- /dev/null
@@ -0,0 +1,72 @@
+package net.sf.openrocket.communication;
+
+import static org.junit.Assert.*;
+
+import java.io.IOException;
+
+import net.sf.openrocket.util.Prefs;
+
+import org.junit.Test;
+
+
+public class BugReportTest {
+       
+       private HttpURLConnectionMock setup() {
+               HttpURLConnectionMock connection = new HttpURLConnectionMock();
+               Communicator.setConnectionSource(new ConnectionSourceStub(connection));
+               
+               connection.setUseCaches(true);
+               return connection;
+       }
+       
+       private void check(HttpURLConnectionMock connection) {
+               assertEquals(Communicator.BUG_REPORT_URL, connection.getTrueUrl());
+               assertTrue(connection.getConnectTimeout() > 0);
+               assertEquals(Prefs.getVersion(), connection.getRequestProperty("X-OpenRocket-Version"));
+               assertTrue(connection.getInstanceFollowRedirects());
+               assertEquals("POST", connection.getRequestMethod());
+               assertFalse(connection.getUseCaches());
+       }
+       
+
+       @Test
+       public void testBugReportSuccess() throws IOException {
+               HttpURLConnectionMock connection = setup();
+               connection.setResponseCode(Communicator.BUG_REPORT_RESPONSE_CODE);
+               
+               String message = 
+                       "MyMessage\n"+
+                       "is important\n"+
+                       "h\u00e4h?";
+               
+               BugReporter.sendBugReport(message);
+
+               check(connection);
+               
+               String msg = connection.getOutputStreamString();
+               assertTrue(msg.indexOf("version=" + Prefs.getVersion()) >= 0);
+               assertTrue(msg.indexOf(Communicator.encode(message)) >= 0);
+       }
+       
+
+       @Test
+       public void testBugReportFailure() throws IOException {
+               HttpURLConnectionMock connection = setup();
+               connection.setResponseCode(200);
+               
+               String message = 
+                       "MyMessage\n"+
+                       "is important\n"+
+                       "h\u00e4h?";
+               
+               try {
+                       BugReporter.sendBugReport(message);
+                       fail("Exception did not occur");
+               } catch (IOException e) {
+                       // Success
+               }
+
+               check(connection);
+       }
+       
+}
diff --git a/test/net/sf/openrocket/communication/CommunicationTest.java b/test/net/sf/openrocket/communication/CommunicationTest.java
deleted file mode 100644 (file)
index 8a2b9be..0000000
+++ /dev/null
@@ -1,120 +0,0 @@
-package net.sf.openrocket.communication;
-
-import static org.junit.Assert.*;
-
-import java.io.IOException;
-import java.io.StringReader;
-import java.util.Random;
-
-import org.junit.Test;
-
-public class CommunicationTest {
-
-       @Test
-       public void testIllegalInputUpdateParsing() throws IOException {
-               
-               UpdateInfo info;
-               
-               info = Communication.parseUpdateInput(new StringReader(""));
-               assertNull(info);
-               
-               info = Communication.parseUpdateInput(new StringReader("vj\u00e4avdsads"));
-               assertNull(info);
-               
-               info = Communication.parseUpdateInput(new StringReader("\u0000\u0001\u0002"));
-               assertNull(info);
-               
-               info = Communication.parseUpdateInput(new StringReader("Version: 1.2"));
-               assertNull(info);
-               
-               info = Communication.parseUpdateInput(new StringReader("Version: 1.2pre"));
-               assertNull(info);
-               
-               info = Communication.parseUpdateInput(new StringReader("Version: 1.2.x"));
-               assertNull(info);
-               
-               info = Communication.parseUpdateInput(new StringReader("\u0000\u0001\u0002"));
-               assertNull(info);
-               
-               // Feed random bad input
-               Random rnd = new Random();
-               StringBuilder sb = new StringBuilder(10000);
-               for (int i=0; i<100; i++) {
-                       int length = rnd.nextInt(10000);
-                       sb.delete(0, sb.length());
-                       for (int j=0; j<length; j++) {
-                               sb.append((char)rnd.nextInt());
-                       }
-                       info = Communication.parseUpdateInput(new StringReader(sb.toString()));
-                       assertNull(info);
-               }
-               
-       }
-       
-       
-
-       @Test
-       public void testValidInputUpdateParsing() throws IOException {
-
-               UpdateInfo info;
-               
-               info = Communication.parseUpdateInput(new StringReader("Version: 1.2.3"));
-               assertNotNull(info);
-               assertEquals("1.2.3", info.getLatestVersion());
-               assertEquals(0, info.getUpdates().size());
-               
-               info = Communication.parseUpdateInput(new StringReader("Version: 1.2.3pre"));
-               assertNotNull(info);
-               assertEquals("1.2.3pre", info.getLatestVersion());
-               assertEquals(0, info.getUpdates().size());
-               
-               info = Communication.parseUpdateInput(new StringReader("Version: 1.2.3-build-3"));
-               assertNotNull(info);
-               assertEquals("1.2.3-build-3", info.getLatestVersion());
-               assertEquals(0, info.getUpdates().size());
-               
-               info = Communication.parseUpdateInput(new StringReader("Version: 1.2.3x\n\n"));
-               assertNotNull(info);
-               assertEquals("1.2.3x", info.getLatestVersion());
-               assertEquals(0, info.getUpdates().size());
-               
-               info = Communication.parseUpdateInput(new StringReader("Version:1.2.3\nfdsacd\u00e4fdsa"));
-               assertNotNull(info);
-               assertEquals("1.2.3", info.getLatestVersion());
-               assertEquals(0, info.getUpdates().size());
-               
-               info = Communication.parseUpdateInput(new StringReader(
-                               "Version:   1.2.3  \n" +
-                               "15: Fifteen\n" +
-                               "3: Three1  \r\n" +
-                               "3: Three2\r" +
-                               "1:One"));
-               assertNotNull(info);
-               assertEquals("1.2.3", info.getLatestVersion());
-               assertEquals(4, info.getUpdates().size());
-               assertEquals(15, info.getUpdates().get(0).getU());
-               assertEquals(3, info.getUpdates().get(1).getU());
-               assertEquals(3, info.getUpdates().get(2).getU());
-               assertEquals(1, info.getUpdates().get(3).getU());
-               assertEquals("Fifteen", info.getUpdates().get(0).getV());
-               assertEquals("Three1", info.getUpdates().get(1).getV());
-               assertEquals("Three2", info.getUpdates().get(2).getV());
-               assertEquals("One", info.getUpdates().get(3).getV());
-               
-
-               info = Communication.parseUpdateInput(new StringReader(
-                               "Version: 1.2.3\n" +
-                               "15:   (C) 1234 A&B %23 \\o/  \r\r\n" +
-                               "5: m\u00e4c\n" +
-                               "3: Invalid\u0000value\n" +
-                               "1: One\u0019two"));
-               assertNotNull(info);
-               assertEquals("1.2.3", info.getLatestVersion());
-               assertEquals(1, info.getUpdates().size());
-               assertEquals(15, info.getUpdates().get(0).getU());
-               assertEquals("(C) 1234 A&B %23 \\o/", info.getUpdates().get(0).getV());
-               
-               
-               
-       }
-}
diff --git a/test/net/sf/openrocket/communication/ConnectionSourceStub.java b/test/net/sf/openrocket/communication/ConnectionSourceStub.java
new file mode 100644 (file)
index 0000000..4cd5470
--- /dev/null
@@ -0,0 +1,22 @@
+package net.sf.openrocket.communication;
+
+import java.io.IOException;
+import java.net.HttpURLConnection;
+
+public class ConnectionSourceStub implements ConnectionSource {
+       
+       private final HttpURLConnection connection;
+
+       public ConnectionSourceStub(HttpURLConnection connection) {
+               this.connection = connection;
+       }
+       
+       @Override
+       public HttpURLConnection getConnection(String url) throws IOException {
+               if (connection instanceof HttpURLConnectionMock) {
+                       ((HttpURLConnectionMock)connection).setTrueUrl(url);
+               }
+               return connection;
+       }
+
+}
diff --git a/test/net/sf/openrocket/communication/HttpURLConnectionMock.java b/test/net/sf/openrocket/communication/HttpURLConnectionMock.java
new file mode 100644 (file)
index 0000000..0ae317b
--- /dev/null
@@ -0,0 +1,548 @@
+package net.sf.openrocket.communication;
+
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.UnsupportedEncodingException;
+import java.net.HttpURLConnection;
+import java.net.MalformedURLException;
+import java.net.ProtocolException;
+import java.net.URL;
+import java.security.Permission;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+public class HttpURLConnectionMock extends HttpURLConnection {
+
+       private static final URL MOCK_URL;
+       static {
+               try {
+                       MOCK_URL = new URL("http://localhost/");
+               } catch (MalformedURLException e) {
+                       throw new RuntimeException(e);
+               }
+       }
+       
+       private volatile boolean instanceFollowRedirects = false;
+       private volatile String requestMethod = "";
+       private volatile int responseCode;
+       private Map<String, String> requestProperties = new HashMap<String, String>();
+       private volatile int connectTimeout = -1;
+       private volatile String contentEncoding = "";
+       
+       private volatile boolean doInput = false;
+       private volatile boolean doOutput = false;
+       
+       private volatile byte[] content = null;
+       private volatile String contentType = null;
+       private volatile boolean useCaches = false;
+       
+       
+       private volatile InputStream inputStream = null;
+       private volatile ByteArrayOutputStream outputStream = null;
+       
+       private volatile String trueUrl = null;
+       
+       
+       private volatile boolean connected = false;
+       private volatile int connectionDelay = 0;
+       
+       private volatile boolean failed = false;
+       
+       
+       
+       
+       public HttpURLConnectionMock() {
+               super(MOCK_URL);
+       }
+       
+       public HttpURLConnectionMock(URL u) {
+               super(u);
+       }
+       
+       
+       
+       public String getTrueUrl() {
+               return trueUrl;
+       }
+       
+       public void setTrueUrl(String url) {
+               assertNull(this.trueUrl);
+               this.trueUrl = url;
+       }
+       
+       
+       public boolean hasFailed() {
+               return failed;
+       }
+       
+       
+       public void setConnectionDelay(int delay) {
+               this.connectionDelay = delay;
+       }
+       
+       
+
+       @Override
+       public void connect() {
+               if (!connected) {
+                       try {
+                               Thread.sleep(connectionDelay);
+                       } catch (InterruptedException e) {
+                       }
+                       connected = true;
+               }
+       }
+
+       @Override
+       public void disconnect() {
+               
+       }
+
+       @Override
+       public boolean usingProxy() {
+               return false;
+       }
+
+       
+       
+       
+       @Override
+       public boolean getInstanceFollowRedirects() {
+               return this.instanceFollowRedirects;
+       }
+
+       @Override
+       public void setInstanceFollowRedirects(boolean followRedirects) {
+               assertFalse(connected);
+               this.instanceFollowRedirects = followRedirects;
+       }
+
+       @Override
+       public String getRequestMethod() {
+               return this.requestMethod; 
+       }
+
+       @Override
+       public void setRequestMethod(String method) throws ProtocolException {
+               assertFalse(connected);
+               this.requestMethod = method;
+       }
+
+       @Override
+       public int getResponseCode() throws IOException {
+               connect();
+               return this.responseCode;
+       }
+
+       public void setResponseCode(int code) {
+               this.responseCode = code;
+       }
+       
+
+       @Override
+       public void addRequestProperty(String key, String value) {
+               assertFalse(connected);
+               assertFalse(this.requestProperties.containsKey(key.toLowerCase()));
+               this.requestProperties.put(key.toLowerCase(), value);
+       }
+
+
+       @Override
+       public void setRequestProperty(String key, String value) {
+               assertFalse(connected);
+               this.requestProperties.put(key.toLowerCase(), value);
+       }
+
+
+       @Override
+       public String getRequestProperty(String key) {
+               return this.requestProperties.get(key.toLowerCase());
+       }
+
+
+       @Override
+       public int getConnectTimeout() {
+               return this.connectTimeout;
+       }
+
+       @Override
+       public void setConnectTimeout(int timeout) {
+               assertFalse(connected);
+               this.connectTimeout = timeout;
+       }
+
+
+
+       @Override
+       public String getContentEncoding() {
+               connect();
+               return this.contentEncoding;
+       }
+       
+       public void setContentEncoding(String encoding) {
+               this.contentEncoding = encoding;
+       }
+
+
+
+       @Override
+       public int getContentLength() {
+               connect();
+               if (content == null)
+                       return 0;
+               return content.length;
+       }
+
+       public void setContent(byte[] content) {
+               this.content = content;
+       }
+       
+       public void setContent(String content) {
+               try {
+                       this.content = content.getBytes("UTF-8");
+               } catch (UnsupportedEncodingException e) {
+                       fail("UTF-8");
+               }
+       }
+
+
+       @Override
+       public String getContentType() {
+               connect();
+               return this.contentType;
+       }
+       
+       public void setContentType(String type) {
+               this.contentType = type;
+       }
+
+
+
+       @Override
+       public boolean getDoInput() {
+               return this.doInput;
+       }
+
+
+       @Override
+       public void setDoInput(boolean doinput) {
+               assertFalse(connected);
+               this.doInput = doinput;
+       }
+
+
+       @Override
+       public boolean getDoOutput() {
+               return this.doOutput;
+       }
+
+
+       @Override
+       public void setDoOutput(boolean dooutput) {
+               assertFalse(connected);
+               this.doOutput = dooutput;
+       }
+
+
+       @Override
+       public InputStream getInputStream() throws IOException {
+               assertTrue(doInput);
+               assertNull(inputStream);
+               assertNotNull(content);
+               
+               connect();
+               inputStream = new ByteArrayInputStream(content);
+               return inputStream;
+       }
+
+
+       @Override
+       public OutputStream getOutputStream() throws IOException {
+               assertTrue(doOutput);
+               assertNull(outputStream);
+               outputStream = new ByteArrayOutputStream();
+               return outputStream;
+       }
+       
+       public byte[] getOutputStreamData() {
+               return outputStream.toByteArray();
+       }
+       
+       public String getOutputStreamString() {
+               try {
+                       return outputStream.toString("UTF-8");
+               } catch (UnsupportedEncodingException e) {
+                       fail("UTF-8");
+                       return null;
+               }
+       }
+
+
+
+       @Override
+       public void setUseCaches(boolean usecaches) {
+               assertFalse(connected);
+               this.useCaches = usecaches;
+       }
+
+
+
+       @Override
+       public boolean getUseCaches() {
+               return this.useCaches;
+       }
+
+
+
+
+
+
+
+
+       private void assertNull(Object o) {
+               try {
+                       org.junit.Assert.assertNull(o);
+               } catch (AssertionError e) {
+                       failed = true;
+                       throw e;
+               }
+       }
+
+       private void assertNotNull(Object o) {
+               try {
+                       org.junit.Assert.assertNotNull(o);
+               } catch (AssertionError e) {
+                       failed = true;
+                       throw e;
+               }
+       }
+
+       private void assertTrue(boolean o) {
+               try {
+                       org.junit.Assert.assertTrue(o);
+               } catch (AssertionError e) {
+                       failed = true;
+                       throw e;
+               }
+       }
+
+       private void assertFalse(boolean o) {
+               try {
+                       org.junit.Assert.assertFalse(o);
+               } catch (AssertionError e) {
+                       failed = true;
+                       throw e;
+               }
+       }
+
+       private void fail(String msg) {
+               failed = true;
+               org.junit.Assert.fail(msg);
+       }
+
+
+
+       
+
+
+
+
+
+       
+       
+       
+       
+
+       
+       
+
+
+       @Override
+       public InputStream getErrorStream() {
+               throw new UnsupportedOperationException();
+       }
+
+
+
+       @Override
+       public String getHeaderField(int n) {
+               throw new UnsupportedOperationException();
+       }
+
+
+
+       @Override
+       public long getHeaderFieldDate(String name, long Default) {
+               throw new UnsupportedOperationException();
+       }
+
+
+
+       @Override
+       public String getHeaderFieldKey(int n) {
+               throw new UnsupportedOperationException();
+       }
+
+
+       @Override
+       public Permission getPermission() throws IOException {
+               throw new UnsupportedOperationException();
+       }
+
+
+       @Override
+       public String getResponseMessage() throws IOException {
+               throw new UnsupportedOperationException();
+       }
+
+
+
+       @Override
+       public void setChunkedStreamingMode(int chunklen) {
+               throw new UnsupportedOperationException();
+       }
+
+
+
+       @Override
+       public void setFixedLengthStreamingMode(int contentLength) {
+               throw new UnsupportedOperationException();
+       }
+
+
+
+
+
+       @Override
+       public boolean getAllowUserInteraction() {
+               throw new UnsupportedOperationException();
+       }
+
+
+
+       @Override
+       public Object getContent() throws IOException {
+               throw new UnsupportedOperationException();
+       }
+
+
+
+       @SuppressWarnings("unchecked")
+       @Override
+       public Object getContent(Class[] classes) throws IOException {
+               throw new UnsupportedOperationException();
+       }
+
+
+       @Override
+       public long getDate() {
+               throw new UnsupportedOperationException();
+       }
+
+
+
+       @Override
+       public boolean getDefaultUseCaches() {
+               throw new UnsupportedOperationException();
+       }
+
+
+       @Override
+       public long getExpiration() {
+               throw new UnsupportedOperationException();
+       }
+
+
+
+       @Override
+       public String getHeaderField(String name) {
+               throw new UnsupportedOperationException();
+       }
+
+
+
+       @Override
+       public int getHeaderFieldInt(String name, int Default) {
+               throw new UnsupportedOperationException();
+       }
+
+
+
+       @Override
+       public Map<String, List<String>> getHeaderFields() {
+               throw new UnsupportedOperationException();
+       }
+
+
+
+       @Override
+       public long getIfModifiedSince() {
+               throw new UnsupportedOperationException();
+       }
+
+
+       @Override
+       public long getLastModified() {
+               throw new UnsupportedOperationException();
+       }
+
+       @Override
+       public int getReadTimeout() {
+               throw new UnsupportedOperationException();
+       }
+
+
+
+       @Override
+       public Map<String, List<String>> getRequestProperties() {
+               throw new UnsupportedOperationException();
+       }
+
+
+       @Override
+       public URL getURL() {
+               throw new UnsupportedOperationException();
+       }
+
+
+
+       @Override
+       public void setAllowUserInteraction(boolean allowuserinteraction) {
+               throw new UnsupportedOperationException();
+       }
+
+       @Override
+       public void setDefaultUseCaches(boolean defaultusecaches) {
+               throw new UnsupportedOperationException();
+       }
+
+
+       @Override
+       public void setIfModifiedSince(long ifmodifiedsince) {
+               throw new UnsupportedOperationException();
+       }
+
+
+       @Override
+       public void setReadTimeout(int timeout) {
+               throw new UnsupportedOperationException();
+       }
+
+
+
+
+
+       @Override
+       public String toString() {
+               throw new UnsupportedOperationException();
+       }
+
+       
+       
+       
+}
diff --git a/test/net/sf/openrocket/communication/UpdateInfoTest.java b/test/net/sf/openrocket/communication/UpdateInfoTest.java
new file mode 100644 (file)
index 0000000..19fed4d
--- /dev/null
@@ -0,0 +1,229 @@
+package net.sf.openrocket.communication;
+
+import static org.junit.Assert.*;
+
+import java.io.IOException;
+import java.util.Collections;
+import java.util.List;
+import java.util.Random;
+
+import net.sf.openrocket.util.ComparablePair;
+import net.sf.openrocket.util.Prefs;
+
+import org.junit.Test;
+
+public class UpdateInfoTest {
+       
+       /** The connection delay */
+       private static final int DELAY = 100;
+       
+       /** How much long does the test allow it to take */
+       private static final int ALLOWANCE = 2000;
+
+       
+       private HttpURLConnectionMock setup() {
+               HttpURLConnectionMock connection = new HttpURLConnectionMock();
+               Communicator.setConnectionSource(new ConnectionSourceStub(connection));
+               
+               connection.setConnectionDelay(DELAY);
+               connection.setUseCaches(true);
+               connection.setContentType("text/plain");
+               return connection;
+       }
+       
+       private void check(HttpURLConnectionMock connection) {
+               assertEquals(Communicator.UPDATE_INFO_URL + "?version=" + Prefs.getVersion(),
+                               connection.getTrueUrl());
+               assertTrue(connection.getConnectTimeout() > 0);
+               assertEquals(Prefs.getVersion(), connection.getRequestProperty("X-OpenRocket-Version"));
+               assertNotNull(connection.getRequestProperty("X-OpenRocket-Country"));
+               assertNotNull(connection.getRequestProperty("X-OpenRocket-ID"));
+               assertNotNull(connection.getRequestProperty("X-OpenRocket-OS"));
+               assertNotNull(connection.getRequestProperty("X-OpenRocket-Java"));
+               assertTrue(connection.getInstanceFollowRedirects());
+               assertEquals("GET", connection.getRequestMethod());
+               assertFalse(connection.getUseCaches());
+       }
+       
+
+       @Test
+       public void testUpdateAvailable() throws IOException {
+               HttpURLConnectionMock connection = setup();
+               connection.setResponseCode(Communicator.UPDATE_INFO_UPDATE_AVAILABLE);
+               
+               String content =
+                       "Version: 6.6.6pre A \n" +
+                       "Extra:  information\n" +
+                       "100:hundred\n" +
+                       "50:  m\u00e4 \n\n" +
+                       "1:     one\n" +
+                       "-2: none";
+               connection.setContent(content);
+               
+               UpdateInfoRetriever retriever = new UpdateInfoRetriever();
+               retriever.start();
+               
+               // Info is null while processing
+               assertNull(retriever.getUpdateInfo());
+               
+               waitfor(retriever);
+               assertFalse(connection.hasFailed());
+
+               UpdateInfo info = retriever.getUpdateInfo();
+               assertNotNull(info);
+
+               check(connection);
+
+               assertEquals("6.6.6pre A", info.getLatestVersion());
+
+               List<ComparablePair<Integer, String>> updates = info.getUpdates();
+               assertEquals(3, updates.size());
+               Collections.sort(updates);
+               assertEquals(1, (int)updates.get(0).getU());
+               assertEquals("one", updates.get(0).getV());
+               assertEquals(50, (int)updates.get(1).getU());
+               assertEquals("m\u00e4", updates.get(1).getV());
+               assertEquals(100, (int)updates.get(2).getU());
+               assertEquals("hundred", updates.get(2).getV());
+       }
+       
+       
+       
+
+       @Test
+       public void testUpdateNotAvailable() throws IOException {
+               HttpURLConnectionMock connection = setup();
+               connection.setResponseCode(Communicator.UPDATE_INFO_NO_UPDATE_CODE);
+               
+               String content =
+                       "Version: 6.6.6pre A \n" +
+                       "Extra:  information\n" +
+                       "100:hundred\n" +
+                       "50:  m\u00e4 \n\n" +
+                       "1:     one\n" +
+                       "-2: none";
+               connection.setContent(content);
+               
+               UpdateInfoRetriever retriever = new UpdateInfoRetriever();
+               retriever.start();
+               
+               // Info is null while processing
+               assertNull(retriever.getUpdateInfo());
+               
+               waitfor(retriever);
+               assertFalse(connection.hasFailed());
+
+               UpdateInfo info = retriever.getUpdateInfo();
+               assertNotNull(info);
+
+               check(connection);
+
+               assertEquals(Prefs.getVersion(), info.getLatestVersion());
+               assertEquals(0, info.getUpdates().size());
+       }
+       
+       
+       
+       @Test
+       public void testInvalidResponses() {
+               HttpURLConnectionMock connection = setup();
+               connection.setResponseCode(404);
+               connection.setContent("Version: 1.2.3");
+
+               UpdateInfoRetriever retriever = new UpdateInfoRetriever();
+               retriever.start();
+               assertNull(retriever.getUpdateInfo());
+               waitfor(retriever);
+               assertFalse(connection.hasFailed());
+               assertNull(retriever.getUpdateInfo());
+               check(connection);
+
+               
+               connection = setup();
+               connection.setResponseCode(Communicator.UPDATE_INFO_UPDATE_AVAILABLE);
+               connection.setContentType("text/xml");
+
+               retriever = new UpdateInfoRetriever();
+               retriever.start();
+               assertNull(retriever.getUpdateInfo());
+               waitfor(retriever);
+               assertFalse(connection.hasFailed());
+               assertNull(retriever.getUpdateInfo());
+               check(connection);
+
+
+
+               connection = setup();
+               connection.setResponseCode(Communicator.UPDATE_INFO_UPDATE_AVAILABLE);
+               String content =
+                       "100:hundred\n" +
+                       "50:  m\u00e4 \n\n" +
+                       "1:     one\n";
+               connection.setContent(content);
+
+               retriever = new UpdateInfoRetriever();
+               retriever.start();
+               assertNull(retriever.getUpdateInfo());
+               waitfor(retriever);
+               assertFalse(connection.hasFailed());
+               assertNull(retriever.getUpdateInfo());
+               check(connection);
+
+
+               connection = setup();
+               connection.setResponseCode(Communicator.UPDATE_INFO_UPDATE_AVAILABLE);
+               connection.setContent(new byte[0]);
+
+               retriever = new UpdateInfoRetriever();
+               retriever.start();
+               assertNull(retriever.getUpdateInfo());
+               waitfor(retriever);
+               assertFalse(connection.hasFailed());
+               assertNull(retriever.getUpdateInfo());
+               check(connection);
+               
+       }
+       
+       @Test
+       public void testRandomInputData() {
+               
+               Random rnd = new Random();
+               for (int i=0; i<10; i++) {
+                       int size = (int) ((1 + 0.3 * rnd.nextGaussian()) * Math.pow(i, 6));
+                       byte[] buf = new byte[size];
+                       rnd.nextBytes(buf);
+
+                       HttpURLConnectionMock connection = setup();
+                       connection.setResponseCode(Communicator.UPDATE_INFO_UPDATE_AVAILABLE);
+                       connection.setContent(buf);
+
+                       UpdateInfoRetriever retriever = new UpdateInfoRetriever();
+                       retriever.start();
+                       assertNull(retriever.getUpdateInfo());
+                       waitfor(retriever);
+                       assertFalse(connection.hasFailed());
+                       assertNull(retriever.getUpdateInfo());
+                       check(connection);
+               }
+               
+       }
+       
+
+       
+       private void waitfor(UpdateInfoRetriever retriever) {
+               long t = System.currentTimeMillis();
+               
+               while (retriever.isRunning()) {
+                       if (System.currentTimeMillis() >= t+ALLOWANCE) {
+                               fail("retriever took too long to respond");
+                       }
+
+                       try {
+                               Thread.sleep(10);
+                       } catch (InterruptedException e) { }
+               }
+               
+               System.out.println("Waiting took " + (System.currentTimeMillis()-t) + " ms");
+       }
+
+}
index d1f94d94556032ad93226cdfaf386a033c231b91..d09e90ab6abadf5f699ec438dca8520dd0530842 100644 (file)
@@ -13,6 +13,9 @@ public class ComponentCompareTest {
 
        @Test
        public void testComponentEquality() {
+               
+               System.out.println("TEST CLASSPATH: " + System.getProperty("java.class.path"));
+               
                Rocket r1 = net.sf.openrocket.util.TestRockets.makeBigBlue();
                Rocket r2 = net.sf.openrocket.util.TestRockets.makeBigBlue();