From: plaa Date: Sun, 4 Oct 2009 15:46:32 +0000 (+0000) Subject: updates for 0.9.4 X-Git-Tag: upstream/1.0.0~15 X-Git-Url: https://git.gag.com/?a=commitdiff_plain;h=d8776d61bfa934a68829d9971bb89311e55c67cf;hp=b3c3c1071dfdca4c6b3eb9935dc461201abdaf60;p=debian%2Fopenrocket updates for 0.9.4 git-svn-id: https://openrocket.svn.sourceforge.net/svnroot/openrocket/trunk@26 180e2498-e6e9-4542-8430-84ac67f01cd8 --- diff --git a/.classpath b/.classpath index fc1bd663..fdfb5235 100644 --- a/.classpath +++ b/.classpath @@ -1,13 +1,17 @@ - + - - + + + + + + diff --git a/ChangeLog b/ChangeLog index 45192f7b..99ac81b1 100644 --- 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 69092107..9bca110f 100644 --- 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 diff --git a/build.properties b/build.properties index d74b630a..16dafdf9 100644 --- a/build.properties +++ b/build.properties @@ -10,3 +10,7 @@ build.version=0.9.4pre build.source=default + +# Whether checking for updates is enabled by default. + +build.checkupdates=true diff --git a/build.xml b/build.xml index 97f5a4e4..b77dd04f 100644 --- a/build.xml +++ b/build.xml @@ -33,9 +33,11 @@ + - + + @@ -51,9 +53,9 @@ Compiling main classes - + Compiling startup classes - + @@ -150,7 +152,7 @@ ${criticaltodos} Building unit tests - + Running unit tests diff --git a/extra-lib/RXTXcomm.jar b/extra-lib/RXTXcomm.jar deleted file mode 100644 index 84e5f01d..00000000 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 index 0fba1356..00000000 --- a/extra-src/altimeter/Alt15K.java +++ /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 - */ - -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 list = new ArrayList();; - - 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 data = new ArrayList(); - 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 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"); - System.err.println("Files will be saved -old.log, -med and -new"); - return; - } - - - String device = null; - String[] devices = Alt15K.getNames(); - for (int i=0; i= 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 index 63314c7a..00000000 --- a/extra-src/altimeter/AltData.java +++ /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 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 - */ - -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 list = new ArrayList();; - - 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 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 - */ - -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 list = new ArrayList();; - - 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 + */ + +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 list = new ArrayList();; + + 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 data = new ArrayList(); + 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 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"); + System.err.println("Files will be saved -old.log, -med and -new"); + return; + } + + + String device = null; + String[] devices = Alt15K.getNames(); + for (int i=0; i= 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 index 00000000..63314c7a --- /dev/null +++ b/src-extra/altimeter/AltData.java @@ -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 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 + */ + +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 list = new ArrayList();; + + 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 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 + */ + +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 list = new ArrayList();; + + 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; itrue 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. - *

- * This method will return null 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 null 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 null 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> updates = - new ArrayList>(); - - 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(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 index 00000000..24ed1f56 --- /dev/null +++ b/src/net/sf/openrocket/communication/Communicator.java @@ -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 index 00000000..b5b0fdcc --- /dev/null +++ b/src/net/sf/openrocket/communication/ConnectionSource.java @@ -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 + */ +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 index 00000000..501c37ac --- /dev/null +++ b/src/net/sf/openrocket/communication/DefaultConnectionSource.java @@ -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 + */ +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 index 00000000..d67ea335 --- /dev/null +++ b/src/net/sf/openrocket/communication/UpdateInfoRetriever.java @@ -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 true 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. + *

+ * This method will return null 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 null 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 null 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> updates = + new ArrayList>(); + + 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(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 + */ + 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> updates = + new ArrayList>(); + + 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(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(); + } + } + } + } +} diff --git a/src/net/sf/openrocket/database/Databases.java b/src/net/sf/openrocket/database/Databases.java index f3fa4bcf..9ec9f589 100644 --- a/src/net/sf/openrocket/database/Databases.java +++ b/src/net/sf/openrocket/database/Databases.java @@ -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 * null 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 index 00000000..59fdbfa7 --- /dev/null +++ b/src/net/sf/openrocket/gui/components/HtmlLabel.java @@ -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 + */ +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 index 0978252a..00000000 --- a/src/net/sf/openrocket/gui/components/ResizeLabel.java +++ /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. - *

- * A nice small text is achievable by new ResizeLabel("My text", -2); - * - * @author Sampo Niskanen - */ - -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 index 00000000..cad8a25a --- /dev/null +++ b/src/net/sf/openrocket/gui/components/StyledLabel.java @@ -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. + *

+ * A nice small text is achievable by new ResizeLabel("My text", -2); + * + * @author Sampo Niskanen + */ + +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("") && str.indexOf(" */ -public class UnitSelector extends ResizeLabel implements ChangeListener, MouseListener, +public class UnitSelector extends StyledLabel implements ChangeListener, MouseListener, ItemSelectable { private DoubleModel model; diff --git a/src/net/sf/openrocket/gui/configdialog/FinSetConfig.java b/src/net/sf/openrocket/gui/configdialog/FinSetConfig.java index efec1267..df5ce634 100644 --- a/src/net/sf/openrocket/gui/configdialog/FinSetConfig.java +++ b/src/net/sf/openrocket/gui/configdialog/FinSetConfig.java @@ -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("Through-the-wall fin tabs:"), "spanx, wrap 30lp"); + panel.add(new StyledLabel("Through-the-wall fin tabs:", Style.BOLD), + "spanx, wrap 30lp"); JLabel label; DoubleModel m; diff --git a/src/net/sf/openrocket/gui/configdialog/FreeformFinSetConfig.java b/src/net/sf/openrocket/gui/configdialog/FreeformFinSetConfig.java index 18b18032..52e02686 100644 --- a/src/net/sf/openrocket/gui/configdialog/FreeformFinSetConfig.java +++ b/src/net/sf/openrocket/gui/configdialog/FreeformFinSetConfig.java @@ -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; } diff --git a/src/net/sf/openrocket/gui/configdialog/ParachuteConfig.java b/src/net/sf/openrocket/gui/configdialog/ParachuteConfig.java index f41ca5ce..d3b37bcb 100644 --- a/src/net/sf/openrocket/gui/configdialog/ParachuteConfig.java +++ b/src/net/sf/openrocket/gui/configdialog/ParachuteConfig.java @@ -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("Canopy:"), "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("Drag coefficient CD:"); + JLabel label = new HtmlLabel("Drag coefficient CD:"); String tip = "The drag coefficient relative to the total area of the parachute.
" + "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("Shroud lines:"), "wrap unrel"); + panel.add(new StyledLabel("Shroud lines:", Style.BOLD), "wrap unrel"); panel.add(new JLabel("Number of lines:")); diff --git a/src/net/sf/openrocket/gui/configdialog/StreamerConfig.java b/src/net/sf/openrocket/gui/configdialog/StreamerConfig.java index 02011b71..5f94bbb9 100644 --- a/src/net/sf/openrocket/gui/configdialog/StreamerConfig.java +++ b/src/net/sf/openrocket/gui/configdialog/StreamerConfig.java @@ -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("Drag coefficient CD:"); + JLabel label = new HtmlLabel("Drag coefficient CD:"); String tip = "The drag coefficient relative to the total area of the streamer.
" + "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"); diff --git a/src/net/sf/openrocket/gui/dialogs/AboutDialog.java b/src/net/sf/openrocket/gui/dialogs/AboutDialog.java index 516be5ec..41014f49 100644 --- a/src/net/sf/openrocket/gui/dialogs/AboutDialog.java +++ b/src/net/sf/openrocket/gui/dialogs/AboutDialog.java @@ -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"); diff --git a/src/net/sf/openrocket/gui/dialogs/BugReportDialog.java b/src/net/sf/openrocket/gui/dialogs/BugReportDialog.java index 6df68031..9ebd9f2a 100644 --- a/src/net/sf/openrocket/gui/dialogs/BugReportDialog.java +++ b/src/net/sf/openrocket/gui/dialogs/BugReportDialog.java @@ -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, diff --git a/src/net/sf/openrocket/gui/dialogs/ComponentAnalysisDialog.java b/src/net/sf/openrocket/gui/dialogs/ComponentAnalysisDialog.java index 81577443..cd7d9c98 100644 --- a/src/net/sf/openrocket/gui/dialogs/ComponentAnalysisDialog.java +++ b/src/net/sf/openrocket/gui/dialogs/ComponentAnalysisDialog.java @@ -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); diff --git a/src/net/sf/openrocket/gui/dialogs/CustomMaterialDialog.java b/src/net/sf/openrocket/gui/dialogs/CustomMaterialDialog.java index 7fb7ad40..ef22d845 100644 --- a/src/net/sf/openrocket/gui/dialogs/CustomMaterialDialog.java +++ b/src/net/sf/openrocket/gui/dialogs/CustomMaterialDialog.java @@ -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"); } diff --git a/src/net/sf/openrocket/gui/dialogs/LicenseDialog.java b/src/net/sf/openrocket/gui/dialogs/LicenseDialog.java index b58113da..10759a69 100644 --- a/src/net/sf/openrocket/gui/dialogs/LicenseDialog.java +++ b/src/net/sf/openrocket/gui/dialogs/LicenseDialog.java @@ -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 { diff --git a/src/net/sf/openrocket/gui/dialogs/MotorChooserDialog.java b/src/net/sf/openrocket/gui/dialogs/MotorChooserDialog.java index 6893dccf..887085df 100644 --- a/src/net/sf/openrocket/gui/dialogs/MotorChooserDialog.java +++ b/src/net/sf/openrocket/gui/dialogs/MotorChooserDialog.java @@ -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 index 00000000..518403c9 --- /dev/null +++ b/src/net/sf/openrocket/gui/dialogs/UpdateInfoDialog.java @@ -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("OpenRocket version " + info.getLatestVersion() + + " is available!"), "wrap para"); + + List> 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); + } + +} diff --git a/src/net/sf/openrocket/gui/dialogs/preferences/PreferencesDialog.java b/src/net/sf/openrocket/gui/dialogs/preferences/PreferencesDialog.java index 2ef8e113..38cc36fe 100644 --- a/src/net/sf/openrocket/gui/dialogs/preferences/PreferencesDialog.java +++ b/src/net/sf/openrocket/gui/dialogs/preferences/PreferencesDialog.java @@ -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"); diff --git a/src/net/sf/openrocket/gui/main/BasicFrame.java b/src/net/sf/openrocket/gui/main/BasicFrame.java index 8ad7601a..fcf74ca2 100644 --- a/src/net/sf/openrocket/gui/main/BasicFrame.java +++ b/src/net/sf/openrocket/gui/main/BasicFrame.java @@ -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(); } diff --git a/src/net/sf/openrocket/gui/main/ComponentAddButtons.java b/src/net/sf/openrocket/gui/main/ComponentAddButtons.java index f1402256..a8072de4 100644 --- a/src/net/sf/openrocket/gui/main/ComponentAddButtons.java +++ b/src/net/sf/openrocket/gui/main/ComponentAddButtons.java @@ -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

The data will be plotted in time order " + + StyledLabel desc = new StyledLabel("

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 index 00000000..4e264fa1 --- /dev/null +++ b/src/net/sf/openrocket/util/LimitedInputStream.java @@ -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 + */ +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"); + } + +} diff --git a/src/net/sf/openrocket/util/Prefs.java b/src/net/sf/openrocket/util/Prefs.java index 2b7c4101..f4f26133 100644 --- a/src/net/sf/openrocket/util/Prefs.java +++ b/src/net/sf/openrocket/util/Prefs.java @@ -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); } diff --git a/src/net/sf/openrocket/utils/MotorCompare.java b/src/net/sf/openrocket/utils/MotorCompare.java index 28894cac..356014fa 100644 --- a/src/net/sf/openrocket/utils/MotorCompare.java +++ b/src/net/sf/openrocket/utils/MotorCompare.java @@ -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 index 00000000..991b8fd2 --- /dev/null +++ b/test/net/sf/openrocket/communication/BugReportTest.java @@ -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 index 8a2b9beb..00000000 --- a/test/net/sf/openrocket/communication/CommunicationTest.java +++ /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 requestProperties = new HashMap(); + 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> 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> 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 index 00000000..19fed4d0 --- /dev/null +++ b/test/net/sf/openrocket/communication/UpdateInfoTest.java @@ -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> 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"); + } + +} diff --git a/test/net/sf/openrocket/rocketcomponent/ComponentCompareTest.java b/test/net/sf/openrocket/rocketcomponent/ComponentCompareTest.java index d1f94d94..d09e90ab 100644 --- a/test/net/sf/openrocket/rocketcomponent/ComponentCompareTest.java +++ b/test/net/sf/openrocket/rocketcomponent/ComponentCompareTest.java @@ -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();