diff --git a/.classpath b/.classpath index fc1bd663e..fdfb5235a 100644 --- a/.classpath +++ b/.classpath @@ -1,13 +1,17 @@ - + - - + + + + + + diff --git a/ChangeLog b/ChangeLog index 45192f7ba..99ac81b15 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 690921077..9bca110f2 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 d74b630a0..16dafdf9e 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 97f5a4e41..b77dd04ff 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/lib-extra/RXTXcomm.jar similarity index 100% rename from extra-lib/RXTXcomm.jar rename to lib-extra/RXTXcomm.jar diff --git a/lib-test/hamcrest-core-1.1.jar b/lib-test/hamcrest-core-1.1.jar new file mode 100644 index 000000000..5f1d5ce0c Binary files /dev/null and b/lib-test/hamcrest-core-1.1.jar differ diff --git a/lib-test/hamcrest-library-1.1.jar b/lib-test/hamcrest-library-1.1.jar new file mode 100644 index 000000000..40610c9b4 Binary files /dev/null and b/lib-test/hamcrest-library-1.1.jar differ diff --git a/lib-test/jmock-2.5.1.jar b/lib-test/jmock-2.5.1.jar new file mode 100644 index 000000000..4415dfbc9 Binary files /dev/null and b/lib-test/jmock-2.5.1.jar differ diff --git a/lib-test/jmock-junit4-2.5.1.jar b/lib-test/jmock-junit4-2.5.1.jar new file mode 100644 index 000000000..fb3697af4 Binary files /dev/null and b/lib-test/jmock-junit4-2.5.1.jar differ diff --git a/lib-test/junit-4.7.jar b/lib-test/junit-4.7.jar new file mode 100644 index 000000000..700ad6952 Binary files /dev/null and b/lib-test/junit-4.7.jar differ diff --git a/extra-src/altimeter/Alt15K.java b/src-extra/altimeter/Alt15K.java similarity index 100% rename from extra-src/altimeter/Alt15K.java rename to src-extra/altimeter/Alt15K.java diff --git a/extra-src/altimeter/AltData.java b/src-extra/altimeter/AltData.java similarity index 100% rename from extra-src/altimeter/AltData.java rename to src-extra/altimeter/AltData.java diff --git a/extra-src/altimeter/RotationLogger.java b/src-extra/altimeter/RotationLogger.java similarity index 100% rename from extra-src/altimeter/RotationLogger.java rename to src-extra/altimeter/RotationLogger.java diff --git a/extra-src/altimeter/SerialDownload.java b/src-extra/altimeter/SerialDownload.java similarity index 100% rename from extra-src/altimeter/SerialDownload.java rename to src-extra/altimeter/SerialDownload.java diff --git a/src/net/sf/openrocket/communication/BugReporter.java b/src/net/sf/openrocket/communication/BugReporter.java new file mode 100644 index 000000000..048701a94 --- /dev/null +++ b/src/net/sf/openrocket/communication/BugReporter.java @@ -0,0 +1,58 @@ +package net.sf.openrocket.communication; + +import java.io.IOException; +import java.io.OutputStreamWriter; +import java.net.HttpURLConnection; + +import net.sf.openrocket.util.Prefs; + +public class BugReporter extends Communicator { + + // Inhibit instantiation + private BugReporter() { + } + + + /** + * Send the provided report to the OpenRocket bug report URL. If the connection + * fails or the server does not respond with the correct response code, an + * exception is thrown. + * + * @param report the report to send. + * @throws IOException if an error occurs while connecting to the server or + * the server responds with a wrong response code. + */ + public static void sendBugReport(String report) throws IOException { + + HttpURLConnection connection = connectionSource.getConnection(BUG_REPORT_URL); + + connection.setConnectTimeout(CONNECTION_TIMEOUT); + connection.setInstanceFollowRedirects(true); + connection.setRequestMethod("POST"); + connection.setUseCaches(false); + connection.setRequestProperty("X-OpenRocket-Version", encode(Prefs.getVersion())); + + String post; + post = (VERSION_PARAM + "=" + encode(Prefs.getVersion()) + + "&" + BUG_REPORT_PARAM + "=" + encode(report)); + + OutputStreamWriter wr = null; + try { + // Send post information + connection.setDoOutput(true); + wr = new OutputStreamWriter(connection.getOutputStream(), "UTF-8"); + wr.write(post); + wr.flush(); + + if (connection.getResponseCode() != BUG_REPORT_RESPONSE_CODE) { + throw new IOException("Server responded with code " + + connection.getResponseCode() + ", expecting " + BUG_REPORT_RESPONSE_CODE); + } + } finally { + if (wr != null) + wr.close(); + connection.disconnect(); + } + } + +} diff --git a/src/net/sf/openrocket/communication/Communication.java b/src/net/sf/openrocket/communication/Communication.java deleted file mode 100644 index df3b0ac18..000000000 --- a/src/net/sf/openrocket/communication/Communication.java +++ /dev/null @@ -1,262 +0,0 @@ -package net.sf.openrocket.communication; - -import java.io.BufferedReader; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.io.OutputStreamWriter; -import java.io.Reader; -import java.io.UnsupportedEncodingException; -import java.net.HttpURLConnection; -import java.net.URL; -import java.net.URLEncoder; -import java.util.ArrayList; - -import net.sf.openrocket.util.ComparablePair; -import net.sf.openrocket.util.Prefs; - -public class Communication { - - private static final String BUG_REPORT_URL = - "http://openrocket.sourceforge.net/actions/reportbug"; - private static final String UPDATE_INFO_URL = - "http://openrocket.sourceforge.net/actions/updates"; - - private static final String VERSION_PARAM = "version"; - - - private static final String BUG_REPORT_PARAM = "content"; - private static final int BUG_REPORT_RESPONSE_CODE = HttpURLConnection.HTTP_ACCEPTED; - private static final int CONNECTION_TIMEOUT = 10000; // in milliseconds - - private static final int UPDATE_INFO_UPDATE_AVAILABLE = HttpURLConnection.HTTP_OK; - private static final int UPDATE_INFO_NO_UPDATE_CODE = HttpURLConnection.HTTP_NO_CONTENT; - private static final String UPDATE_INFO_CONTENT_TYPE = "text/plain"; - - - private static UpdateInfoFetcher fetcher = null; - - - /** - * Send the provided report to the OpenRocket bug report URL. If the connection - * fails or the server does not respond with the correct response code, an - * exception is thrown. - * - * @param report the report to send. - * @throws IOException if an error occurs while connecting to the server or - * the server responds with a wrong response code. - */ - public static void sendBugReport(String report) throws IOException { - URL url = new URL(BUG_REPORT_URL); - - HttpURLConnection connection = (HttpURLConnection) url.openConnection(); - - connection.setConnectTimeout(CONNECTION_TIMEOUT); - connection.setInstanceFollowRedirects(true); - connection.setRequestMethod("POST"); - connection.setUseCaches(false); - connection.setRequestProperty("X-OpenRocket-Version", encode(Prefs.getVersion())); - - String post; - post = (VERSION_PARAM + "=" + encode(Prefs.getVersion()) - + "&" + BUG_REPORT_PARAM + "=" + encode(report)); - - OutputStreamWriter wr = null; - try { - // Send post information - connection.setDoOutput(true); - wr = new OutputStreamWriter(connection.getOutputStream(), "UTF-8"); - wr.write(post); - wr.flush(); - - if (connection.getResponseCode() != BUG_REPORT_RESPONSE_CODE) { - throw new IOException("Server responded with code " + - connection.getResponseCode() + ", expecting " + BUG_REPORT_RESPONSE_CODE); - } - } finally { - if (wr != null) - wr.close(); - connection.disconnect(); - } - } - - - - /** - * Start an asynchronous task that will fetch information about the latest - * OpenRocket version. This will overwrite any previous fetching operation. - */ - public static void startFetchUpdateInfo() { - fetcher = new UpdateInfoFetcher(); - fetcher.start(); - } - - - /** - * Check whether the update info fetching is still in progress. - * - * @return true 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 000000000..24ed1f56f --- /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 000000000..b5b0fdccc --- /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 000000000..501c37ac7 --- /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 000000000..d67ea3354 --- /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 f3fa4bcf9..9ec9f589a 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 000000000..59fdbfa72 --- /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 0978252a5..000000000 --- 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 000000000..cad8a25ab --- /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 efec12679..df5ce634f 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 18b18032a..52e02686a 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 f41ca5ce2..d3b37bcbe 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 02011b71f..5f94bbb9d 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 516be5ec3..41014f49d 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 6df680314..9ebd9f2a8 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 815774434..cd7d9c98b 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 7fb7ad400..ef22d8450 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 b58113da5..10759a69f 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 6893dccf5..887085df9 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 000000000..518403c96 --- /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 2ef8e113e..38cc36fe7 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 8ad7601ad..fcf74ca29 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(); - - // Starting action + + // Load motors etc. + Databases.fakeMethod(); + + // 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 f14022566..a8072de47 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 000000000..4e264fa19 --- /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 2b7c4101d..f4f26133d 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 28894cac0..356014faf 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 000000000..991b8fd25 --- /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 8a2b9beb6..000000000 --- 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 000000000..19fed4d0c --- /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 d1f94d945..d09e90ab6 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();