diff --git a/core/.classpath b/core/.classpath index e6289b42e..79e3ec49c 100644 --- a/core/.classpath +++ b/core/.classpath @@ -29,5 +29,7 @@ + + diff --git a/core/build.xml b/core/build.xml index 25ff866a7..2b761b6e7 100644 --- a/core/build.xml +++ b/core/build.xml @@ -101,6 +101,7 @@ + diff --git a/core/lib-test/test-plugin.jar b/core/lib-test/test-plugin.jar new file mode 100644 index 000000000..1a6f40aaf Binary files /dev/null and b/core/lib-test/test-plugin.jar differ diff --git a/core/lib/annotation-detector-3.0.2-SNAPSHOT.jar b/core/lib/annotation-detector-3.0.2-SNAPSHOT.jar new file mode 100644 index 000000000..bab8dc3e1 Binary files /dev/null and b/core/lib/annotation-detector-3.0.2-SNAPSHOT.jar differ diff --git a/core/src/net/sf/openrocket/formatting/MotorDescriptionSubstitutor.java b/core/src/net/sf/openrocket/formatting/MotorDescriptionSubstitutor.java index 7b4f69924..fb38e8b1a 100644 --- a/core/src/net/sf/openrocket/formatting/MotorDescriptionSubstitutor.java +++ b/core/src/net/sf/openrocket/formatting/MotorDescriptionSubstitutor.java @@ -33,7 +33,7 @@ public class MotorDescriptionSubstitutor implements RocketSubstitutor { @Override public String substitute(String str, Rocket rocket, String configId) { String description = getFlightConfigurationDescription(rocket, configId); - return str.replaceAll(SUBSTITUTION, description); + return str.replace(SUBSTITUTION, description); } @Override diff --git a/core/src/net/sf/openrocket/gui/print/FinMarkingGuide.java b/core/src/net/sf/openrocket/gui/print/FinMarkingGuide.java index 64b617ab1..d2509c145 100644 --- a/core/src/net/sf/openrocket/gui/print/FinMarkingGuide.java +++ b/core/src/net/sf/openrocket/gui/print/FinMarkingGuide.java @@ -1,15 +1,5 @@ package net.sf.openrocket.gui.print; -import net.sf.openrocket.l10n.Translator; -import net.sf.openrocket.rocketcomponent.BodyTube; -import net.sf.openrocket.rocketcomponent.ExternalComponent; -import net.sf.openrocket.rocketcomponent.FinSet; -import net.sf.openrocket.rocketcomponent.LaunchLug; -import net.sf.openrocket.rocketcomponent.Rocket; -import net.sf.openrocket.rocketcomponent.RocketComponent; -import net.sf.openrocket.startup.Application; - -import javax.swing.JPanel; import java.awt.BasicStroke; import java.awt.Color; import java.awt.Graphics; @@ -21,12 +11,22 @@ import java.awt.geom.Path2D; import java.awt.image.BufferedImage; import java.util.ArrayList; import java.util.Collections; -import java.util.Comparator; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import javax.swing.JPanel; + +import net.sf.openrocket.l10n.Translator; +import net.sf.openrocket.rocketcomponent.BodyTube; +import net.sf.openrocket.rocketcomponent.ExternalComponent; +import net.sf.openrocket.rocketcomponent.FinSet; +import net.sf.openrocket.rocketcomponent.LaunchLug; +import net.sf.openrocket.rocketcomponent.Rocket; +import net.sf.openrocket.rocketcomponent.RocketComponent; +import net.sf.openrocket.startup.Application; + /** * This is the core Swing representation of a fin marking guide. It can handle multiple fin sets on the same or * different body tubes. One marking guide will be created for any body tube that has a finset. If a tube has multiple @@ -36,448 +36,446 @@ import java.util.Map; *

*/ public class FinMarkingGuide extends JPanel { - - /** - * The stroke of normal lines. - */ - private final static BasicStroke thinStroke = new BasicStroke(1.0f); - - /** - * The size of the arrow in points. - */ - private static final int ARROW_SIZE = 10; - - /** - * Typical thickness of a piece of printer paper (~20-24 lb paper). Wrapping paper around a tube results in the - * radius being increased by the thickness of the paper. The smaller the tube, the more pronounced this becomes as a - * percentage of circumference. Using 1/10mm as an approximation here. - */ - private static final double PAPER_THICKNESS_IN_METERS = PrintUnit.MILLIMETERS.toMeters(0.1d); - - /** - * The default guide width in inches. - */ - public final static double DEFAULT_GUIDE_WIDTH = 3d; - - /** - * 2 PI radians (represents a circle). - */ - public final static double TWO_PI = 2 * Math.PI; - - /** - * The I18N translator. - */ - private static final Translator trans = Application.getTranslator(); - - /** - * The margin. - */ - private static final int MARGIN = (int) PrintUnit.INCHES.toPoints(0.25f); - - /** - * The height (circumference) of the biggest body tube with a finset. - */ - private int maxHeight = 0; - - /** - * A map of body tubes, to a list of components that contains finsets and launch lugs. - */ - private Map> markingGuideItems; - - /** - * Constructor. - * - * @param rocket the rocket instance - */ - public FinMarkingGuide(Rocket rocket) { - super(false); - setBackground(Color.white); - markingGuideItems = init(rocket); - //Max of 2 drawing guides horizontally per page. - setSize((int) PrintUnit.INCHES.toPoints(DEFAULT_GUIDE_WIDTH) * 2 + 3 * MARGIN, maxHeight); - } - - /** - * Initialize the marking guide class by iterating over a rocket and finding all finsets. - * - * @param component the root rocket component - this is iterated to find all finset and launch lugs - * - * @return a map of body tubes to lists of finsets and launch lugs. - */ - private Map> init(Rocket component) { - Iterator iter = component.iterator(false); - Map> results = new LinkedHashMap>(); - BodyTube current = null; - int totalHeight = 0; - int iterationHeight = 0; - int count = 0; - - while (iter.hasNext()) { - RocketComponent next = iter.next(); - if (next instanceof BodyTube) { - current = (BodyTube) next; - } - else if (next instanceof FinSet || next instanceof LaunchLug) { - java.util.List list = results.get(current); - if (list == null && current != null) { - list = new ArrayList(); - results.put(current, list); - - double radius = current.getOuterRadius(); - int circumferenceInPoints = (int) PrintUnit.METERS.toPoints(radius * TWO_PI); - - // Find the biggest body tube circumference. - if (iterationHeight < (circumferenceInPoints + MARGIN)) { - iterationHeight = circumferenceInPoints + MARGIN; - } - //At most, two marking guides horizontally. After that, move down and back to the left margin. - count++; - if (count % 2 == 0) { - totalHeight += iterationHeight; - iterationHeight = 0; - } - } - if (list != null) { - list.add((ExternalComponent) next); - } - } - } - maxHeight = totalHeight + iterationHeight; - return results; - } - - /** - * Returns a generated image of the fin marking guide. May then be used wherever AWT images can be used, or - * converted to another image/picture format and used accordingly. - * - * @return an awt image of the fin marking guide - */ - public Image createImage() { - int width = getWidth() + 25; - int height = getHeight() + 25; - // Create a buffered image in which to draw - BufferedImage bufferedImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB); - // Create a graphics context on the buffered image - Graphics2D g2d = bufferedImage.createGraphics(); - // Draw graphics - g2d.setBackground(Color.white); - g2d.clearRect(0, 0, width, height); - paintComponent(g2d); - // Graphics context no longer needed so dispose it - g2d.dispose(); - return bufferedImage; - } - - /** - *

-     *   ---------------------- Page Edge --------------------------------------------------------
-     *   |                                        ^
-     *   |                                        |
-     *   |
-     *   |                                        y
-     *   |
-     *   |                                        |
-     *   P                                        v
-     *   a      ---                 +----------------------------+  ------------
-     *   g<------^-- x ------------>+                            +       ^
-     *   e       |                  +                            +       |
-     *           |                  +                            +     baseYOffset
-     *   E       |                  +                            +       v
-     *   d       |                  +<----------Fin------------->+ -------------
-     *   g       |                  +                            +
-     *   e       |                  +                            +
-     *   |       |                  +                            +
-     *   |       |                  +                            +
-     *   |       |                  +                            +   baseSpacing
-     *   |       |                  +                            +
-     *   |       |                  +                            +
-     *   |       |                  +                            +
-     *   |       |                  +                            +
-     *   |       |                  +<----------Fin------------->+  --------------
-     *   |       |                  +                            +
-     *   | circumferenceInPoints    +                            +
-     *   |       |                  +                            +
-     *   |       |                  +                            +
-     *   |       |                  +                            +    baseSpacing
-     *   |       |                  +<------Launch Lug --------->+           -----
-     *   |       |                  +                            +                 \
-     *   |       |                  +                            +                 + yLLOffset
-     *   |       |                  +                            +                 /
-     *   |       |                  +<----------Fin------------->+  --------------
-     *   |       |                  +                            +       ^
-     *   |       |                  +                            +       |
-     *   |       |                  +                            +    baseYOffset
-     *   |       v                  +                            +       v
-     *   |      ---                 +----------------------------+  --------------
-     *
-     *                              |<-------- width ----------->|
-     *
-     * yLLOffset is computed from the difference between the base rotation of the fin and the radial direction of the
-     * lug.
-     *
-     * Note: There is a current limitation that a tube with multiple launch lugs may not render the lug lines
-     * correctly.
-     * 
- * - * @param g the Graphics context - */ - @Override - public void paintComponent(Graphics g) { - super.paintComponent(g); - Graphics2D g2 = (Graphics2D) g; - paintFinMarkingGuide(g2); - } - - private void paintFinMarkingGuide(Graphics2D g2) { - g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, - RenderingHints.VALUE_ANTIALIAS_ON); - - g2.setColor(Color.BLACK); - g2.setStroke(thinStroke); - int x = MARGIN; - int y = MARGIN; - - int width = (int) PrintUnit.INCHES.toPoints(DEFAULT_GUIDE_WIDTH); - - int column = 0; - - for (BodyTube next : markingGuideItems.keySet()) { - double circumferenceInPoints = PrintUnit.METERS.toPoints((next.getOuterRadius() + PAPER_THICKNESS_IN_METERS) * - TWO_PI); - List componentList = markingGuideItems.get(next); - //Don't draw the lug if there are no fins. - if (hasFins(componentList)) { - - drawMarkingGuide(g2, x, y, (int) Math.ceil(circumferenceInPoints), width); - - //Sort so that fins always precede lugs - sort(componentList); - - boolean hasMultipleComponents = componentList.size() > 1; - - double baseSpacing = 0d; - double baseYOrigin = 0; - double finRadial = 0d; - int yFirstFin = y; - int yLastFin = y; - boolean firstFinSet = true; - - //fin1: 42 fin2: 25 - for (ExternalComponent externalComponent : componentList) { - if (externalComponent instanceof FinSet) { - FinSet fins = (FinSet) externalComponent; - int finCount = fins.getFinCount(); - int offset = 0; - baseSpacing = (circumferenceInPoints / finCount); - double baseRotation = fins.getBaseRotation(); - if (!firstFinSet) { - //Adjust the rotation to a positive number. - while (baseRotation < 0) { - baseRotation += TWO_PI / finCount; - } - offset = computeYOffset(y, circumferenceInPoints, baseSpacing, baseYOrigin, finRadial, baseRotation); - } - else { - //baseYOrigin is the distance from the top of the marking guide to the first fin of the first fin set. - //This measurement is used to base all subsequent finsets and lugs off of. - baseYOrigin = baseSpacing / 2; - offset = (int) (baseYOrigin) + y; - firstFinSet = false; - } - yFirstFin = y; - yLastFin = y; - finRadial = baseRotation; - - //Draw the fin marking lines. - for (int fin = 0; fin < finCount; fin++) { - if (fin > 0) { - offset += baseSpacing; - yLastFin = offset; - } - else { - yFirstFin = offset; - } - drawDoubleArrowLine(g2, x, offset, x + width, offset); - // if (hasMultipleComponents) { - g2.drawString(externalComponent.getName(), x + (width / 3), offset - 2); - // } - } - } - else if (externalComponent instanceof LaunchLug) { - LaunchLug lug = (LaunchLug) externalComponent; - double yLLOffset = (lug.getRadialDirection() - finRadial) / TWO_PI; - //The placement of the lug line must respect the boundary of the outer marking guide. In order - //to do that, place it above or below either the top or bottom fin line, based on the difference - //between their rotational directions. - if (yLLOffset < 0) { - yLLOffset = yLLOffset * circumferenceInPoints + yLastFin; - } - else { - yLLOffset = yLLOffset * circumferenceInPoints + yFirstFin; - } - drawDoubleArrowLine(g2, x, (int) yLLOffset, x + width, (int) yLLOffset); - g2.drawString(lug.getName(), x + (width / 3), (int) yLLOffset - 2); - - } - } - //Only if the tube has a lug or multiple finsets does the orientation of the marking guide matter. So print 'Front'. - if (hasMultipleComponents) { - drawFrontIndication(g2, x, y, (int) baseSpacing, (int) circumferenceInPoints, width); - } - - //At most, two marking guides horizontally. After that, move down and back to the left margin. - column++; - if (column % 2 == 0) { - x = MARGIN; - y += circumferenceInPoints + MARGIN; - } - else { - x += MARGIN + width; - } - } - } - } - - /** - * Compute the y offset for the next fin line. - * - * @param y the top margin - * @param circumferenceInPoints the circumference (height) of the guide - * @param baseSpacing the circumference / fin count - * @param baseYOrigin the offset from the top of the guide to the first fin of the first fin set drawn - * @param prevBaseRotation the rotation of the previous finset - * @param baseRotation the rotation of the current finset - * - * @return number of points from the top of the marking guide to the line to be drawn - */ - private int computeYOffset(int y, double circumferenceInPoints, double baseSpacing, double baseYOrigin, double prevBaseRotation, double baseRotation) { - int offset; - double finRadialDifference = (baseRotation - prevBaseRotation) / TWO_PI; - //If the fin line would be off the top of the marking guide, then readjust. - if (baseYOrigin + finRadialDifference * circumferenceInPoints < 0) { - offset = (int) (baseYOrigin + baseSpacing + finRadialDifference * circumferenceInPoints) + y; - } - else if (baseYOrigin - finRadialDifference * circumferenceInPoints > 0) { - offset = (int) (finRadialDifference * circumferenceInPoints + baseYOrigin) + y; - } - else { - offset = (int) (finRadialDifference * circumferenceInPoints - baseYOrigin) + y; - } - return offset; - } - - /** - * Determines if the list contains a FinSet. - * - * @param list a list of ExternalComponent - * - * @return true if the list contains at least one FinSet - */ - private boolean hasFins(List list) { - for (ExternalComponent externalComponent : list) { - if (externalComponent instanceof FinSet) { - return true; - } - } - return false; - } - - /** - * Sort a list of ExternalComponent in-place. Forces FinSets to precede Launch Lugs. - * - * @param componentList a list of ExternalComponent - */ - private void sort(List componentList) { - Collections.sort(componentList, new Comparator() { - @Override - public int compare(ExternalComponent o1, ExternalComponent o2) { - if (o1 instanceof FinSet) { - return -1; - } - if (o2 instanceof FinSet) { - return 1; - } - return 0; - } - }); - } - - /** - * Draw the marking guide outline. - * - * @param g2 the graphics context - * @param x the starting x coordinate - * @param y the starting y coordinate - * @param length the length, or height, in print units of the marking guide; should be equivalent to the outer tube - * circumference - * @param width the width of the marking guide in print units; somewhat arbitrary - */ - private void drawMarkingGuide(Graphics2D g2, int x, int y, int length, int width) { - Path2D outline = new Path2D.Float(GeneralPath.WIND_EVEN_ODD, 4); - outline.moveTo(x, y); - outline.lineTo(width + x, y); - outline.lineTo(width + x, length + y); - outline.lineTo(x, length + y); - outline.closePath(); - g2.draw(outline); - - //Draw tick marks for alignment, 1/4 of the width in from either edge - int fromEdge = (width) / 4; - final int tickLength = 8; - //Upper left - g2.drawLine(x + fromEdge, y, x + fromEdge, y + tickLength); - //Upper right - g2.drawLine(x + width - fromEdge, y, x + width - fromEdge, y + tickLength); - //Lower left - g2.drawLine(x + fromEdge, y + length - tickLength, x + fromEdge, y + length); - //Lower right - g2.drawLine(x + width - fromEdge, y + length - tickLength, x + width - fromEdge, y + length); - } - - /** - * Draw a vertical string indicating the front of the rocket. This is necessary when a launch lug exists to give - * proper orientation of the guide (assuming that the lug is asymmetrically positioned with respect to a fin). - * - * @param g2 the graphics context - * @param x the starting x coordinate - * @param y the starting y coordinate - * @param spacing the space between fin lines - * @param length the length, or height, in print units of the marking guide; should be equivalent to the outer tube - * circumference - * @param width the width of the marking guide in print units; somewhat arbitrary - */ - private void drawFrontIndication(Graphics2D g2, int x, int y, int spacing, int length, int width) { - //The magic numbers here are fairly arbitrary. They're chosen in a manner that best positions 'Front' to be - //readable, without going to complex string layout prediction logic. - int rotateX = x + width - 16; - int rotateY = y + (int) (spacing * 1.5) + 20; - if (rotateY > y + length + 14) { - rotateY = y + length / 2 - 10; - } - g2.translate(rotateX, rotateY); - g2.rotate(Math.PI / 2); - g2.drawString(trans.get("FinMarkingGuide.lbl.Front"), 0, 0); - g2.rotate(-Math.PI / 2); - g2.translate(-rotateX, -rotateY); - } - - /** - * Draw a horizontal line with arrows on both endpoints. Depicts a fin alignment. - * - * @param g2 the graphics context - * @param x1 the starting x coordinate - * @param y1 the starting y coordinate - * @param x2 the ending x coordinate - * @param y2 the ending y coordinate - */ - void drawDoubleArrowLine(Graphics2D g2, int x1, int y1, int x2, int y2) { - int len = x2 - x1; - - g2.drawLine(x1, y1, x1 + len, y2); - g2.fillPolygon(new int[]{x1 + len, x1 + len - ARROW_SIZE, x1 + len - ARROW_SIZE, x1 + len}, - new int[]{y2, y2 - ARROW_SIZE / 2, y2 + ARROW_SIZE / 2, y2}, 4); - - g2.fillPolygon(new int[]{x1, x1 + ARROW_SIZE, x1 + ARROW_SIZE, x1}, - new int[]{y1, y1 - ARROW_SIZE / 2, y1 + ARROW_SIZE / 2, y1}, 4); - } + + /** + * The stroke of normal lines. + */ + private final static BasicStroke thinStroke = new BasicStroke(1.0f); + + /** + * The size of the arrow in points. + */ + private static final int ARROW_SIZE = 10; + + /** + * Typical thickness of a piece of printer paper (~20-24 lb paper). Wrapping paper around a tube results in the + * radius being increased by the thickness of the paper. The smaller the tube, the more pronounced this becomes as a + * percentage of circumference. Using 1/10mm as an approximation here. + */ + private static final double PAPER_THICKNESS_IN_METERS = PrintUnit.MILLIMETERS.toMeters(0.1d); + + /** + * The default guide width in inches. + */ + public final static double DEFAULT_GUIDE_WIDTH = 3d; + + /** + * 2 PI radians (represents a circle). + */ + public final static double TWO_PI = 2 * Math.PI; + public final static double PI = Math.PI; + + /** + * The I18N translator. + */ + private static final Translator trans = Application.getTranslator(); + + /** + * The margin. + */ + private static final int MARGIN = (int) PrintUnit.INCHES.toPoints(0.25f); + + /** + * The height (circumference) of the biggest body tube with a finset. + */ + private int maxHeight = 0; + + /** + * A map of body tubes, to a list of components that contains finsets and launch lugs. + */ + private Map> markingGuideItems; + + /** + * Constructor. + * + * @param rocket the rocket instance + */ + public FinMarkingGuide(Rocket rocket) { + super(false); + setBackground(Color.white); + markingGuideItems = init(rocket); + //Max of 2 drawing guides horizontally per page. + setSize((int) PrintUnit.INCHES.toPoints(DEFAULT_GUIDE_WIDTH) * 2 + 3 * MARGIN, maxHeight); + } + + /** + * Initialize the marking guide class by iterating over a rocket and finding all finsets. + * + * @param component the root rocket component - this is iterated to find all finset and launch lugs + * + * @return a map of body tubes to lists of finsets and launch lugs. + */ + private Map> init(Rocket component) { + Iterator iter = component.iterator(false); + Map> results = new LinkedHashMap>(); + BodyTube current = null; + int totalHeight = 0; + int iterationHeight = 0; + int count = 0; + + while (iter.hasNext()) { + RocketComponent next = iter.next(); + if (next instanceof BodyTube) { + current = (BodyTube) next; + } + else if (next instanceof FinSet || next instanceof LaunchLug) { + java.util.List list = results.get(current); + if (list == null && current != null) { + list = new ArrayList(); + results.put(current, list); + + double radius = current.getOuterRadius(); + int circumferenceInPoints = (int) PrintUnit.METERS.toPoints(radius * TWO_PI); + + // Find the biggest body tube circumference. + if (iterationHeight < (circumferenceInPoints + MARGIN)) { + iterationHeight = circumferenceInPoints + MARGIN; + } + //At most, two marking guides horizontally. After that, move down and back to the left margin. + count++; + if (count % 2 == 0) { + totalHeight += iterationHeight; + iterationHeight = 0; + } + } + if (list != null) { + list.add((ExternalComponent) next); + } + } + } + maxHeight = totalHeight + iterationHeight; + return results; + } + + /** + * Returns a generated image of the fin marking guide. May then be used wherever AWT images can be used, or + * converted to another image/picture format and used accordingly. + * + * @return an awt image of the fin marking guide + */ + public Image createImage() { + int width = getWidth() + 25; + int height = getHeight() + 25; + // Create a buffered image in which to draw + BufferedImage bufferedImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB); + // Create a graphics context on the buffered image + Graphics2D g2d = bufferedImage.createGraphics(); + // Draw graphics + g2d.setBackground(Color.white); + g2d.clearRect(0, 0, width, height); + paintComponent(g2d); + // Graphics context no longer needed so dispose it + g2d.dispose(); + return bufferedImage; + } + + /** + *
+	 *   ---------------------- Page Edge --------------------------------------------------------
+	 *   |                                        ^
+	 *   |                                        |
+	 *   |
+	 *   |                                        y
+	 *   |
+	 *   |                                        |
+	 *   P                                        v
+	 *   a      ---                 +----------------------------+  <- radialOrigin (in radians)
+	 *   g<------^-- x ------------>+                            +
+	 *   e       |                  +                            +
+	 *           |                  +                            +
+	 *   E       |                  +                            +
+	 *   d       |                  +<----------Fin------------->+ <- y+ (finRadialPosition - radialOrigin) / TWO_PI * circumferenceInPoints
+	 *   g       |                  +                            +
+	 *   e       |                  +                            +
+	 *   |       |                  +                            +
+	 *   |       |                  +                            +
+	 *   |       |                  +                            +
+	 *   |       |                  +                            +
+	 *   |       |                  +                            +
+	 *   |       |                  +                            +
+	 *   |       |                  +                            +
+	 *   |       |                  +<----------Fin------------->+
+	 *   |       |                  +                            +
+	 *   | circumferenceInPoints    +                            +
+	 *   |       |                  +                            +
+	 *   |       |                  +                            +
+	 *   |       |                  +                            +
+	 *   |       |                  +<------Launch Lug --------->+
+	 *   |       |                  +                            +
+	 *   |       |                  +                            +
+	 *   |       |                  +                            +
+	 *   |       |                  +<----------Fin------------->+
+	 *   |       |                  +                            +
+	 *   |       |                  +                            +
+	 *   |       |                  +                            +
+	 *   |       v                  +                            +
+	 *   |      ---                 +----------------------------+ <- radialOrigin + TWO_PI
+	 *
+	 *                              |<-------- width ----------->|
+	 *
+	 * yLLOffset is computed from the difference between the base rotation of the fin and the radial direction of the
+	 * lug.
+	 *
+	 * Note: There is a current limitation that a tube with multiple launch lugs may not render the lug lines
+	 * correctly.
+	 * 
+ * + * @param g the Graphics context + */ + @Override + public void paintComponent(Graphics g) { + super.paintComponent(g); + Graphics2D g2 = (Graphics2D) g; + paintFinMarkingGuide(g2); + } + + private void paintFinMarkingGuide(Graphics2D g2) { + g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, + RenderingHints.VALUE_ANTIALIAS_ON); + + g2.setColor(Color.BLACK); + g2.setStroke(thinStroke); + int x = MARGIN; + int y = MARGIN; + + int width = (int) PrintUnit.INCHES.toPoints(DEFAULT_GUIDE_WIDTH); + + int column = 0; + + for (BodyTube next : markingGuideItems.keySet()) { + double circumferenceInPoints = PrintUnit.METERS.toPoints((next.getOuterRadius() + PAPER_THICKNESS_IN_METERS) * + TWO_PI); + List componentList = markingGuideItems.get(next); + //Don't draw the lug if there are no fins. + if (hasFins(componentList)) { + + drawMarkingGuide(g2, x, y, (int) Math.ceil(circumferenceInPoints), width); + + double radialOrigin = findRadialOrigin(componentList); + + boolean hasMultipleComponents = componentList.size() > 1; + + //fin1: 42 fin2: 25 + for (ExternalComponent externalComponent : componentList) { + if (externalComponent instanceof FinSet) { + FinSet fins = (FinSet) externalComponent; + int finCount = fins.getFinCount(); + double baseAngularSpacing = (TWO_PI / finCount); + double baseAngularOffset = fins.getBaseRotation(); + //Draw the fin marking lines. + for (int fin = 0; fin < finCount; fin++) { + double angle = baseAngularOffset + fin * baseAngularSpacing - radialOrigin; + // Translate angle into pixels using a linear transformation: + // radialOrigin -> y + // radialOrigin + TWO_PI -> y + circumferenceInPoints + + while (angle < 0) { + angle += TWO_PI; + } + while (angle > TWO_PI) { + angle -= TWO_PI; + } + + int offset = (int) Math.round(y + angle / TWO_PI * circumferenceInPoints); + + drawDoubleArrowLine(g2, x, offset, x + width, offset); + // if (hasMultipleComponents) { + g2.drawString(externalComponent.getName(), x + (width / 3), offset - 2); + // } + } + } + else if (externalComponent instanceof LaunchLug) { + LaunchLug lug = (LaunchLug) externalComponent; + double angle = lug.getRadialDirection() - radialOrigin; + while (angle < 0) { + angle += TWO_PI; + } + int yLLOffset = (int) Math.round(y + angle / TWO_PI * circumferenceInPoints); + drawDoubleArrowLine(g2, x, (int) yLLOffset, x + width, (int) yLLOffset); + g2.drawString(lug.getName(), x + (width / 3), (int) yLLOffset - 2); + + } + } + //Only if the tube has a lug or multiple finsets does the orientation of the marking guide matter. So print 'Front'. + if (hasMultipleComponents) { + drawFrontIndication(g2, x, y, 0, (int) circumferenceInPoints, width); + } + + //At most, two marking guides horizontally. After that, move down and back to the left margin. + column++; + if (column % 2 == 0) { + x = MARGIN; + y += circumferenceInPoints + MARGIN; + } + else { + x += MARGIN + width; + } + } + } + } + + /** + * This function finds a origin in radians for the template so no component is on the template seam. + * + * If no fin or launch lug is at 0.0 radians, then the origin is 0. If there is one, then half the distance + * between the two are taken. + * + * @param components + * @return + */ + private double findRadialOrigin(List components) { + + ArrayList positions = new ArrayList(3 * components.size()); + + for (ExternalComponent component : components) { + + if (component instanceof LaunchLug) { + double componentPosition = ((LaunchLug) component).getRadialDirection(); + + positions.add(makeZeroTwoPi(componentPosition)); + } + + if (component instanceof FinSet) { + FinSet fins = (FinSet) component; + double basePosition = fins.getBaseRotation(); + double angle = TWO_PI / fins.getFinCount(); + for (int i = fins.getFinCount(); i > 0; i--) { + positions.add(makeZeroTwoPi(basePosition)); + basePosition += angle; + } + } + } + + Collections.sort(positions); + + Double[] pos = positions.toArray(new Double[0]); + + if (pos.length == 1) { + + return makeZeroTwoPi(pos[0] + PI); + + } + + double biggestDistance = TWO_PI - pos[pos.length - 1] + pos[0]; + double center = makeZeroTwoPi(pos[0] - biggestDistance / 2.0); + + for (int i = 1; i < pos.length; i++) { + + double d = pos[i] - pos[i - 1]; + if (d > biggestDistance) { + biggestDistance = d; + center = makeZeroTwoPi(pos[i - 1] + biggestDistance / 2.0); + } + } + + return center; + } + + private static double makeZeroTwoPi(double value) { + + double v = value; + while (v < 0) { + v += TWO_PI; + } + while (v > TWO_PI) { + v -= TWO_PI; + } + + return v; + } + + /** + * Determines if the list contains a FinSet. + * + * @param list a list of ExternalComponent + * + * @return true if the list contains at least one FinSet + */ + private boolean hasFins(List list) { + for (ExternalComponent externalComponent : list) { + if (externalComponent instanceof FinSet) { + return true; + } + } + return false; + } + + /** + * Draw the marking guide outline. + * + * @param g2 the graphics context + * @param x the starting x coordinate + * @param y the starting y coordinate + * @param length the length, or height, in print units of the marking guide; should be equivalent to the outer tube + * circumference + * @param width the width of the marking guide in print units; somewhat arbitrary + */ + private void drawMarkingGuide(Graphics2D g2, int x, int y, int length, int width) { + Path2D outline = new Path2D.Float(GeneralPath.WIND_EVEN_ODD, 4); + outline.moveTo(x, y); + outline.lineTo(width + x, y); + outline.lineTo(width + x, length + y); + outline.lineTo(x, length + y); + outline.closePath(); + g2.draw(outline); + + //Draw tick marks for alignment, 1/4 of the width in from either edge + int fromEdge = (width) / 4; + final int tickLength = 8; + //Upper left + g2.drawLine(x + fromEdge, y, x + fromEdge, y + tickLength); + //Upper right + g2.drawLine(x + width - fromEdge, y, x + width - fromEdge, y + tickLength); + //Lower left + g2.drawLine(x + fromEdge, y + length - tickLength, x + fromEdge, y + length); + //Lower right + g2.drawLine(x + width - fromEdge, y + length - tickLength, x + width - fromEdge, y + length); + } + + /** + * Draw a vertical string indicating the front of the rocket. This is necessary when a launch lug exists to give + * proper orientation of the guide (assuming that the lug is asymmetrically positioned with respect to a fin). + * + * @param g2 the graphics context + * @param x the starting x coordinate + * @param y the starting y coordinate + * @param spacing the space between fin lines + * @param length the length, or height, in print units of the marking guide; should be equivalent to the outer tube + * circumference + * @param width the width of the marking guide in print units; somewhat arbitrary + */ + private void drawFrontIndication(Graphics2D g2, int x, int y, int spacing, int length, int width) { + //The magic numbers here are fairly arbitrary. They're chosen in a manner that best positions 'Front' to be + //readable, without going to complex string layout prediction logic. + int rotateX = x + width - 16; + int rotateY = y + (int) (spacing * 1.5) + 20; + if (rotateY > y + length + 14) { + rotateY = y + length / 2 - 10; + } + g2.translate(rotateX, rotateY); + g2.rotate(Math.PI / 2); + g2.drawString(trans.get("FinMarkingGuide.lbl.Front"), 0, 0); + g2.rotate(-Math.PI / 2); + g2.translate(-rotateX, -rotateY); + } + + /** + * Draw a horizontal line with arrows on both endpoints. Depicts a fin alignment. + * + * @param g2 the graphics context + * @param x1 the starting x coordinate + * @param y1 the starting y coordinate + * @param x2 the ending x coordinate + * @param y2 the ending y coordinate + */ + void drawDoubleArrowLine(Graphics2D g2, int x1, int y1, int x2, int y2) { + int len = x2 - x1; + + g2.drawLine(x1, y1, x1 + len, y2); + g2.fillPolygon(new int[] { x1 + len, x1 + len - ARROW_SIZE, x1 + len - ARROW_SIZE, x1 + len }, + new int[] { y2, y2 - ARROW_SIZE / 2, y2 + ARROW_SIZE / 2, y2 }, 4); + + g2.fillPolygon(new int[] { x1, x1 + ARROW_SIZE, x1 + ARROW_SIZE, x1 }, + new int[] { y1, y1 - ARROW_SIZE / 2, y1 + ARROW_SIZE / 2, y1 }, 4); + } } \ No newline at end of file diff --git a/core/src/net/sf/openrocket/motor/DesignationComparator.java b/core/src/net/sf/openrocket/motor/DesignationComparator.java index 21489cb79..2c0e8f1f4 100644 --- a/core/src/net/sf/openrocket/motor/DesignationComparator.java +++ b/core/src/net/sf/openrocket/motor/DesignationComparator.java @@ -19,9 +19,25 @@ public class DesignationComparator implements Comparator { COLLATOR = Collator.getInstance(Locale.US); COLLATOR.setStrength(Collator.PRIMARY); } - - private Pattern pattern = - Pattern.compile("^([0-9][0-9]+|1/([1-8]))?([a-zA-Z])([0-9]+)(.*?)$"); + + /* + * Regexp to parse the multitude of designations. Supported types: + * + * 1/4A4... + * 1/2A4... + * A4... + * 132G100... + * 132-G100... + * + * Capture groups: + * 1 = garbage (stuff before impulse class) + * 2 = divisor number for 1/2A, 1/4A etc, otherwise null + * 3 = impulse class letter + * 4 = average thrust + * 5 = stuff after thrust number + */ + private Pattern pattern = + Pattern.compile("^([0-9]+-?|1/([1-8]))?([a-zA-Z])([0-9,]+)(.*?)$"); @Override public int compare(String o1, String o2) { @@ -32,13 +48,13 @@ public class DesignationComparator implements Comparator { m2 = pattern.matcher(o2); if (m1.find() && m2.find()) { - + String o1Class = m1.group(3); - int o1Thrust = Integer.parseInt(m1.group(4)); + int o1Thrust = Integer.parseInt(m1.group(4).replaceAll(",", "")); String o1Extra = m1.group(5); String o2Class = m2.group(3); - int o2Thrust = Integer.parseInt(m2.group(4)); + int o2Thrust = Integer.parseInt(m2.group(4).replaceAll(",", "")); String o2Extra = m2.group(5); // 1. Motor class @@ -46,18 +62,18 @@ public class DesignationComparator implements Comparator { // 1/2A and 1/4A comparison String sub1 = m1.group(2); String sub2 = m2.group(2); - + if (sub1 != null || sub2 != null) { if (sub1 == null) sub1 = "1"; if (sub2 == null) sub2 = "1"; - value = -COLLATOR.compare(sub1,sub2); + value = -COLLATOR.compare(sub1, sub2); if (value != 0) return value; } } - value = COLLATOR.compare(o1Class,o2Class); + value = COLLATOR.compare(o1Class, o2Class); if (value != 0) return value; diff --git a/core/src/net/sf/openrocket/plugin/AnnotationFinder.java b/core/src/net/sf/openrocket/plugin/AnnotationFinder.java new file mode 100644 index 000000000..6e689e321 --- /dev/null +++ b/core/src/net/sf/openrocket/plugin/AnnotationFinder.java @@ -0,0 +1,16 @@ +package net.sf.openrocket.plugin; + +import java.util.List; + +/** + * Interface for finding annotated classes from the class path. + */ +public interface AnnotationFinder { + + /** + * Return a list of all types (classes and interfaces) that are annotated + * with the provided annotation. + */ + public List> findAnnotatedTypes(Class annotation); + +} diff --git a/core/src/net/sf/openrocket/plugin/AnnotationFinderImpl.java b/core/src/net/sf/openrocket/plugin/AnnotationFinderImpl.java new file mode 100644 index 000000000..b4514e057 --- /dev/null +++ b/core/src/net/sf/openrocket/plugin/AnnotationFinderImpl.java @@ -0,0 +1,91 @@ +package net.sf.openrocket.plugin; + +import java.io.File; +import java.io.IOException; +import java.lang.annotation.Annotation; +import java.net.URL; +import java.net.URLClassLoader; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import net.sf.openrocket.util.BugException; +import net.sf.openrocket.util.JarUtil; +import eu.infomas.annotation.AnnotationDetector; +import eu.infomas.annotation.AnnotationDetector.TypeReporter; + +/** + * An AnnotationFinder that uses annotation-detector library to scan + * the class path. Compatible with the JIJ loader. + */ +public class AnnotationFinderImpl implements AnnotationFinder { + + @Override + public List> findAnnotatedTypes(Class annotation) { + final List> classes = new ArrayList>(); + + TypeReporter reporter = new ListReporter(classes); + final AnnotationDetector cf = new AnnotationDetector(reporter); + try { + ClassLoader loader = this.getClass().getClassLoader(); + if (loader instanceof URLClassLoader) { + + /* + * In case of URLClassLoader (which may be our own instantiation) + * use the URLs from there, as java.class.path may not be up-to-date. + */ + + URLClassLoader urlClassLoader = (URLClassLoader) loader; + URL[] urls = urlClassLoader.getURLs(); + + List files = new ArrayList(); + for (URL url : urls) { + if (url.getProtocol().equals("file")) { + files.add(JarUtil.urlToFile(url)); + } + } + + cf.detect(files.toArray(new File[0])); + } else { + + /* + * If not using a URLClassLoader, just do the default. + */ + cf.detect(); + } + + } catch (IOException e) { + throw new BugException("Unable to search class path", e); + } + + return classes; + } + + + private static class ListReporter implements TypeReporter { + private final List> classes; + private final Set names = new HashSet(); + + public ListReporter(List> classes) { + this.classes = classes; + } + + @SuppressWarnings("unchecked") + @Override + public Class[] annotations() { + return new Class[] { Plugin.class }; + } + + @Override + public void reportTypeAnnotation(Class annotation, String className) { + if (names.add(className)) { + try { + classes.add(this.getClass().getClassLoader().loadClass(className)); + } catch (ClassNotFoundException e) { + // Ignore + } + } + } + } +} diff --git a/core/src/net/sf/openrocket/plugin/JIJ.java b/core/src/net/sf/openrocket/plugin/JIJ.java deleted file mode 100644 index 6d9fec052..000000000 --- a/core/src/net/sf/openrocket/plugin/JIJ.java +++ /dev/null @@ -1,29 +0,0 @@ -package net.sf.openrocket.plugin; - -import java.io.File; -import java.lang.reflect.Method; -import java.net.URL; -import java.net.URLClassLoader; -import java.util.Arrays; - -public class JIJ { - - public static void main(String[] args) throws Exception { - String cp = System.getProperty("java.class.path"); - String[] cps = cp.split(File.pathSeparator); - - URL[] urls = new URL[cps.length + 1]; - for (int i = 0; i < cps.length; i++) { - urls[i] = new File(cps[i]).toURI().toURL(); - } - urls[cps.length] = new File("/home/sampo/Projects/OpenRocket/core/example.jar").toURI().toURL(); - - System.out.println("Classpath: " + Arrays.toString(urls)); - - URLClassLoader loader = new URLClassLoader(urls, null); - Class c = loader.loadClass("net.sf.openrocket.plugin.Test"); - Method m = c.getMethod("main", args.getClass()); - m.invoke(null, (Object) args); - } - -} diff --git a/core/src/net/sf/openrocket/plugin/Plugin.java b/core/src/net/sf/openrocket/plugin/Plugin.java index de8eb4cdd..2dc2aee27 100644 --- a/core/src/net/sf/openrocket/plugin/Plugin.java +++ b/core/src/net/sf/openrocket/plugin/Plugin.java @@ -6,8 +6,10 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** - * Annotation that defines an interface to be a plugin interface. - * Plugin interfaces are automatically discovered from plugin JARs and + * Annotation that defines an interface to be a plugin interface and + * classes as plugin implementations. + *

+ * Plugin interfaces are automatically discovered from the classpath and * registered as plugins in Guice. * * @author Sampo Niskanen diff --git a/core/src/net/sf/openrocket/plugin/PluginModule.java b/core/src/net/sf/openrocket/plugin/PluginModule.java index 5aaa24404..c882f5cba 100644 --- a/core/src/net/sf/openrocket/plugin/PluginModule.java +++ b/core/src/net/sf/openrocket/plugin/PluginModule.java @@ -1,14 +1,9 @@ package net.sf.openrocket.plugin; -import java.io.File; -import java.io.IOException; import java.util.ArrayList; -import java.util.Enumeration; import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.jar.JarEntry; -import java.util.jar.JarFile; import com.google.inject.AbstractModule; import com.google.inject.multibindings.Multibinder; @@ -21,63 +16,46 @@ import com.google.inject.multibindings.Multibinder; */ public class PluginModule extends AbstractModule { - private final List jars; - private final ClassLoader classLoader; private Map, Multibinder> binders = new HashMap, Multibinder>(); - - - /** - * Sole constructor. - * - * @param jars the JAR files to search for plugins - * @param classLoader the class loader used to load classes from the JAR files - */ - public PluginModule(List jars, ClassLoader classLoader) { - this.jars = jars; - this.classLoader = classLoader; - } - - - @Override - protected void configure() { - for (File jar : jars) { - List classNames = readClassNames(jar); - for (String className : classNames) { - checkForPlugin(className); - } - } - } - + private AnnotationFinder finder = new AnnotationFinderImpl(); @SuppressWarnings("unchecked") - private void checkForPlugin(String className) { - try { + @Override + protected void configure() { + + List> classes = finder.findAnnotatedTypes(Plugin.class); + List> interfaces = new ArrayList>(); + List> unusedInterfaces; + + // Find plugin interfaces + for (Class c : classes) { + if (c.isInterface()) { + interfaces.add(c); + } + } + unusedInterfaces = new ArrayList>(interfaces); + + // Find plugin implementations + for (Class c : classes) { + if (c.isInterface()) + continue; - Class c = classLoader.loadClass(className); for (Class intf : c.getInterfaces()) { - System.out.println("Testing class " + c + " interface " + intf); - if (isPluginInterface(intf)) { - System.out.println("BINDING"); + if (interfaces.contains(intf)) { // Ugly hack to enable dynamic binding... Can this be done type-safely? Multibinder binder = (Multibinder) findBinder(intf); binder.addBinding().to(c); + unusedInterfaces.remove(intf); } } - - } catch (ClassNotFoundException e) { - System.err.println("Could not load class " + className + ": " + e); } + + // TODO: Unused plugin interfaces should be bound to an empty set - how? } - - private boolean isPluginInterface(Class intf) { - return intf.isAnnotationPresent(Plugin.class); - } - - private Multibinder findBinder(Class intf) { Multibinder binder = binders.get(intf); if (binder == null) { @@ -87,36 +65,4 @@ public class PluginModule extends AbstractModule { return binder; } - - private List readClassNames(File jar) { - List classNames = new ArrayList(); - - JarFile file = null; - try { - file = new JarFile(jar); - Enumeration entries = file.entries(); - while (entries.hasMoreElements()) { - JarEntry entry = entries.nextElement(); - String name = entry.getName(); - if (name.toLowerCase().endsWith(".class") && !name.contains("$")) { - name = name.substring(0, name.length() - 6); - name = name.replace('/', '.'); - classNames.add(name); - } - } - } catch (IOException e) { - System.err.println("Error reading JAR file " + jar); - } finally { - if (file != null) { - try { - file.close(); - } catch (IOException e) { - // Curse all checked exceptions... - } - } - } - - return classNames; - } - } diff --git a/core/src/net/sf/openrocket/plugin/Test.java b/core/src/net/sf/openrocket/plugin/Test.java deleted file mode 100644 index da2c0386c..000000000 --- a/core/src/net/sf/openrocket/plugin/Test.java +++ /dev/null @@ -1,47 +0,0 @@ -package net.sf.openrocket.plugin; - -import java.io.File; -import java.net.MalformedURLException; -import java.net.URL; -import java.net.URLClassLoader; -import java.util.Arrays; -import java.util.List; -import java.util.Set; - -import com.google.inject.Guice; -import com.google.inject.Inject; -import com.google.inject.Injector; - -public class Test { - - @Inject - private Set impls; - - - public void run() { - System.out.println("Plugin count: " + impls.size()); - for (ExamplePlugin i : impls) { - i.doit(); - } - } - - - public static void main(String[] args) throws MalformedURLException { - // Properties p = System.getProperties(); - // Enumeration e = p.keys(); - // while (e.hasMoreElements()) { - // Object key = e.nextElement(); - // Object value = p.get(key); - // System.out.println(key + " = " + value); - // } - - List jars = Arrays.asList(new File("/home/sampo/Projects/OpenRocket/core/example.jar")); - URL[] urls = { new File("/home/sampo/Projects/OpenRocket/core/example.jar").toURI().toURL() }; - ClassLoader classLoader = new URLClassLoader(urls); - - classLoader = Test.class.getClassLoader(); - - Injector injector = Guice.createInjector(new PluginModule(jars, classLoader)); - injector.getInstance(Test.class).run(); - } -} diff --git a/core/src/net/sf/openrocket/simulation/SimulationOptions.java b/core/src/net/sf/openrocket/simulation/SimulationOptions.java index 0271af62e..63b84864b 100644 --- a/core/src/net/sf/openrocket/simulation/SimulationOptions.java +++ b/core/src/net/sf/openrocket/simulation/SimulationOptions.java @@ -15,6 +15,7 @@ import net.sf.openrocket.models.gravity.GravityModel; import net.sf.openrocket.models.gravity.WGSGravityModel; import net.sf.openrocket.models.wind.PinkNoiseWindModel; import net.sf.openrocket.rocketcomponent.Rocket; +import net.sf.openrocket.startup.Application; import net.sf.openrocket.util.BugException; import net.sf.openrocket.util.ChangeSource; import net.sf.openrocket.util.GeodeticComputationStrategy; @@ -404,7 +405,7 @@ public class SimulationOptions implements ChangeSource, Cloneable { if (src.rocket.hasMotors(src.motorID)) { // Try to find a closely matching motor ID - MotorDescriptionSubstitutor formatter = new MotorDescriptionSubstitutor(); + MotorDescriptionSubstitutor formatter = Application.getInjector().getInstance(MotorDescriptionSubstitutor.class); String motorDesc = formatter.substitute(MotorDescriptionSubstitutor.SUBSTITUTION, src.rocket, src.motorID); String matchID = null; diff --git a/core/src/net/sf/openrocket/startup/GuiceStartup.java b/core/src/net/sf/openrocket/startup/GuiceStartup.java index 5ba0c5c34..865955b4c 100644 --- a/core/src/net/sf/openrocket/startup/GuiceStartup.java +++ b/core/src/net/sf/openrocket/startup/GuiceStartup.java @@ -16,7 +16,6 @@ import net.sf.openrocket.logging.LogHelper; import net.sf.openrocket.logging.LogLevel; import net.sf.openrocket.logging.LogLevelBufferLogger; import net.sf.openrocket.logging.PrintStreamLogger; -import net.sf.openrocket.plugin.PluginHelper; import net.sf.openrocket.plugin.PluginModule; import com.google.inject.Guice; @@ -135,26 +134,27 @@ public class GuiceStartup { " " + LOG_STDERR_PROPERTY + "=" + System.getProperty(LOG_STDERR_PROPERTY) + ")"; log.info(str); - + //Replace System.err with a PrintStream that logs lines to DEBUG, or VBOSE if they are indented. //If debug info is not being output to the console then the data is both logged and written to //stderr. - final boolean writeToStderr = !( printer.getOutput(LogLevel.DEBUG) == System.out || printer.getOutput(LogLevel.DEBUG) == System.err); - final PrintStream stdErr = System.err; + final boolean writeToStderr = !(printer.getOutput(LogLevel.DEBUG) == System.out || printer.getOutput(LogLevel.DEBUG) == System.err); + final PrintStream stdErr = System.err; System.setErr(new PrintStream(new OutputStream() { StringBuilder currentLine = new StringBuilder(); + @Override public synchronized void write(int b) throws IOException { - if ( writeToStderr ){ + if (writeToStderr) { //Write to real stderr stdErr.write(b); } if (b == '\r' || b == '\n') { //Line is complete, log it - if (currentLine.toString().trim().length() > 0){ + if (currentLine.toString().trim().length() > 0) { String s = currentLine.toString(); - if ( Character.isWhitespace(s.charAt(0))){ + if (Character.isWhitespace(s.charAt(0))) { log.verbose(currentLine.toString()); } else { log.debug(currentLine.toString()); @@ -247,7 +247,7 @@ public class GuiceStartup { private static Injector initializeGuice() { Module applicationModule = new ApplicationModule(); - Module pluginModule = new PluginModule(PluginHelper.getPluginJars(), GuiceStartup.class.getClassLoader()); + Module pluginModule = new PluginModule(); return Guice.createInjector(applicationModule, pluginModule); } diff --git a/core/src/net/sf/openrocket/unit/UnitGroup.java b/core/src/net/sf/openrocket/unit/UnitGroup.java index 432398dfb..8c140ae98 100644 --- a/core/src/net/sf/openrocket/unit/UnitGroup.java +++ b/core/src/net/sf/openrocket/unit/UnitGroup.java @@ -1,6 +1,12 @@ package net.sf.openrocket.unit; -import static net.sf.openrocket.util.Chars.*; +import static net.sf.openrocket.util.Chars.CUBED; +import static net.sf.openrocket.util.Chars.DEGREE; +import static net.sf.openrocket.util.Chars.DOT; +import static net.sf.openrocket.util.Chars.MICRO; +import static net.sf.openrocket.util.Chars.PERMILLE; +import static net.sf.openrocket.util.Chars.SQUARED; +import static net.sf.openrocket.util.Chars.ZWSP; import static net.sf.openrocket.util.MathUtil.pow2; import java.util.ArrayList; @@ -86,6 +92,7 @@ public class UnitGroup { static { UNITS_NONE = new UnitGroup(); UNITS_NONE.addUnit(Unit.NOUNIT); + UNITS_NONE.setDefaultUnit(0); UNITS_ENERGY = new UnitGroup(); UNITS_ENERGY.addUnit(new GeneralUnit(1, "J")); @@ -127,9 +134,9 @@ public class UnitGroup { UNITS_LENGTH.setDefaultUnit(1); UNITS_MOTOR_DIMENSIONS = new UnitGroup(); - UNITS_MOTOR_DIMENSIONS.addUnit(new GeneralUnit(1, "m")); // just added UNITS_MOTOR_DIMENSIONS.addUnit(new GeneralUnit(0.001, "mm")); UNITS_MOTOR_DIMENSIONS.addUnit(new GeneralUnit(0.01, "cm")); + UNITS_MOTOR_DIMENSIONS.addUnit(new GeneralUnit(1, "m")); UNITS_MOTOR_DIMENSIONS.addUnit(new GeneralUnit(0.0254, "in")); UNITS_MOTOR_DIMENSIONS.setDefaultUnit(0); @@ -140,6 +147,7 @@ public class UnitGroup { UNITS_DISTANCE.addUnit(new GeneralUnit(0.9144, "yd")); UNITS_DISTANCE.addUnit(new GeneralUnit(1609.344, "mi")); UNITS_DISTANCE.addUnit(new GeneralUnit(1852, "nmi")); + UNITS_DISTANCE.setDefaultUnit(0); UNITS_ALL_LENGTHS = new UnitGroup(); UNITS_ALL_LENGTHS.addUnit(new GeneralUnit(0.001, "mm")); @@ -164,15 +172,16 @@ public class UnitGroup { UNITS_STABILITY = new UnitGroup(); - UNITS_STABILITY.addUnit(new GeneralUnit(1, "m")); UNITS_STABILITY.addUnit(new GeneralUnit(0.001, "mm")); UNITS_STABILITY.addUnit(new GeneralUnit(0.01, "cm")); + UNITS_STABILITY.addUnit(new GeneralUnit(1, "m")); UNITS_STABILITY.addUnit(new GeneralUnit(0.0254, "in")); UNITS_STABILITY.addUnit(new CaliberUnit((Rocket) null)); - UNITS_STABILITY.setDefaultUnit(3); + UNITS_STABILITY.setDefaultUnit(4); UNITS_STABILITY_CALIBERS = new UnitGroup(); UNITS_STABILITY_CALIBERS.addUnit(new GeneralUnit(1, "cal")); + UNITS_STABILITY_CALIBERS.setDefaultUnit(0); UNITS_VELOCITY = new UnitGroup(); @@ -180,23 +189,27 @@ public class UnitGroup { UNITS_VELOCITY.addUnit(new GeneralUnit(1 / 3.6, "km/h")); UNITS_VELOCITY.addUnit(new GeneralUnit(0.3048, "ft/s")); UNITS_VELOCITY.addUnit(new GeneralUnit(0.44704, "mph")); + UNITS_VELOCITY.setDefaultUnit(0); UNITS_WINDSPEED = new UnitGroup(); UNITS_WINDSPEED.addUnit(new GeneralUnit(1, "m/s")); UNITS_WINDSPEED.addUnit(new GeneralUnit(1 / 3.6, "km/h")); UNITS_WINDSPEED.addUnit(new GeneralUnit(0.3048, "ft/s")); UNITS_WINDSPEED.addUnit(new GeneralUnit(0.44704, "mph")); + UNITS_WINDSPEED.setDefaultUnit(0); UNITS_ACCELERATION = new UnitGroup(); UNITS_ACCELERATION.addUnit(new GeneralUnit(1, "m/s" + SQUARED)); UNITS_ACCELERATION.addUnit(new GeneralUnit(0.3048, "ft/s" + SQUARED)); UNITS_ACCELERATION.addUnit(new GeneralUnit(9.80665, "G")); + UNITS_ACCELERATION.setDefaultUnit(0); UNITS_MASS = new UnitGroup(); UNITS_MASS.addUnit(new GeneralUnit(0.001, "g")); UNITS_MASS.addUnit(new GeneralUnit(1, "kg")); UNITS_MASS.addUnit(new GeneralUnit(0.0283495231, "oz")); UNITS_MASS.addUnit(new GeneralUnit(0.45359237, "lb")); + UNITS_MASS.setDefaultUnit(0); UNITS_INERTIA = new UnitGroup(); UNITS_INERTIA.addUnit(new GeneralUnit(0.0001, "kg" + DOT + "cm" + SQUARED)); @@ -211,6 +224,7 @@ public class UnitGroup { UNITS_ANGLE.addUnit(new DegreeUnit()); UNITS_ANGLE.addUnit(new FixedPrecisionUnit("rad", 0.01)); UNITS_ANGLE.addUnit(new GeneralUnit(1.0 / 3437.74677078, "arcmin")); + UNITS_ANGLE.setDefaultUnit(0); UNITS_DENSITY_BULK = new UnitGroup(); UNITS_DENSITY_BULK.addUnit(new GeneralUnit(1000, "g/cm" + CUBED)); @@ -218,6 +232,7 @@ public class UnitGroup { UNITS_DENSITY_BULK.addUnit(new GeneralUnit(1, "kg/m" + CUBED)); UNITS_DENSITY_BULK.addUnit(new GeneralUnit(1729.99404, "oz/in" + CUBED)); UNITS_DENSITY_BULK.addUnit(new GeneralUnit(16.0184634, "lb/ft" + CUBED)); + UNITS_DENSITY_BULK.setDefaultUnit(0); UNITS_DENSITY_SURFACE = new UnitGroup(); UNITS_DENSITY_SURFACE.addUnit(new GeneralUnit(10, "g/cm" + SQUARED)); @@ -232,15 +247,18 @@ public class UnitGroup { UNITS_DENSITY_LINE.addUnit(new GeneralUnit(0.001, "g/m")); UNITS_DENSITY_LINE.addUnit(new GeneralUnit(1, "kg/m")); UNITS_DENSITY_LINE.addUnit(new GeneralUnit(0.0930102465, "oz/ft")); + UNITS_DENSITY_LINE.setDefaultUnit(0); UNITS_FORCE = new UnitGroup(); UNITS_FORCE.addUnit(new GeneralUnit(1, "N")); UNITS_FORCE.addUnit(new GeneralUnit(4.44822162, "lbf")); UNITS_FORCE.addUnit(new GeneralUnit(9.80665, "kgf")); + UNITS_FORCE.setDefaultUnit(0); UNITS_IMPULSE = new UnitGroup(); UNITS_IMPULSE.addUnit(new GeneralUnit(1, "Ns")); UNITS_IMPULSE.addUnit(new GeneralUnit(4.44822162, "lbf" + DOT + "s")); + UNITS_IMPULSE.setDefaultUnit(0); UNITS_TIME_STEP = new UnitGroup(); UNITS_TIME_STEP.addUnit(new FixedPrecisionUnit("ms", 1, 0.001)); @@ -249,10 +267,12 @@ public class UnitGroup { UNITS_SHORT_TIME = new UnitGroup(); UNITS_SHORT_TIME.addUnit(new GeneralUnit(1, "s")); + UNITS_SHORT_TIME.setDefaultUnit(0); UNITS_FLIGHT_TIME = new UnitGroup(); UNITS_FLIGHT_TIME.addUnit(new GeneralUnit(1, "s")); UNITS_FLIGHT_TIME.addUnit(new GeneralUnit(60, "min")); + UNITS_FLIGHT_TIME.setDefaultUnit(0); UNITS_ROLL = new UnitGroup(); UNITS_ROLL.addUnit(new GeneralUnit(1, "rad/s")); @@ -274,32 +294,29 @@ public class UnitGroup { UNITS_PRESSURE.addUnit(new GeneralUnit(3386.389, "inHg")); UNITS_PRESSURE.addUnit(new GeneralUnit(6894.75729, "psi")); UNITS_PRESSURE.addUnit(new GeneralUnit(1, "Pa")); + UNITS_PRESSURE.setDefaultUnit(0); UNITS_RELATIVE = new UnitGroup(); UNITS_RELATIVE.addUnit(new FixedPrecisionUnit("" + ZWSP, 0.01, 1.0)); UNITS_RELATIVE.addUnit(new GeneralUnit(0.01, "%")); UNITS_RELATIVE.addUnit(new FixedPrecisionUnit("" + PERMILLE, 1, 0.001)); - // UNITS_RELATIVE.addUnit(new FixedPrecisionUnit("" + ZWSP, 0.01, 1.0)); - // UNITS_RELATIVE.addUnit(new FixedPrecisionUnit("%", 1, 0.01)); - // UNITS_RELATIVE.addUnit(new FixedPrecisionUnit("" + PERMILLE, 1, 0.001)); UNITS_RELATIVE.setDefaultUnit(1); UNITS_ROUGHNESS = new UnitGroup(); - UNITS_ROUGHNESS.addUnit(new GeneralUnit(1, "m")); // just added UNITS_ROUGHNESS.addUnit(new GeneralUnit(0.000001, MICRO + "m")); UNITS_ROUGHNESS.addUnit(new GeneralUnit(0.0000254, "mil")); + UNITS_ROUGHNESS.addUnit(new GeneralUnit(1, "m")); + UNITS_ROUGHNESS.setDefaultUnit(0); UNITS_COEFFICIENT = new UnitGroup(); UNITS_COEFFICIENT.addUnit(new FixedPrecisionUnit("" + ZWSP, 0.01)); // zero-width space + UNITS_COEFFICIENT.setDefaultUnit(0); // This is not used by OpenRocket, and not extensively tested: UNITS_FREQUENCY = new UnitGroup(); - // UNITS_FREQUENCY.addUnit(new GeneralUnit(1, "s")); - // UNITS_FREQUENCY.addUnit(new GeneralUnit(0.001, "ms")); - // UNITS_FREQUENCY.addUnit(new GeneralUnit(0.000001, MICRO + "s")); UNITS_FREQUENCY.addUnit(new FrequencyUnit(.001, "mHz")); UNITS_FREQUENCY.addUnit(new FrequencyUnit(1, "Hz")); UNITS_FREQUENCY.addUnit(new FrequencyUnit(1000, "kHz")); diff --git a/core/test/net/sf/openrocket/database/ThrustCurveMotorSetTest.java b/core/test/net/sf/openrocket/database/ThrustCurveMotorSetTest.java index 40bf5e942..a544e44b2 100644 --- a/core/test/net/sf/openrocket/database/ThrustCurveMotorSetTest.java +++ b/core/test/net/sf/openrocket/database/ThrustCurveMotorSetTest.java @@ -1,6 +1,10 @@ package net.sf.openrocket.database; -import static org.junit.Assert.*; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; import java.util.Arrays; import java.util.Collections; @@ -14,31 +18,31 @@ import net.sf.openrocket.util.Coordinate; import org.junit.Test; public class ThrustCurveMotorSetTest { - - + + private static final ThrustCurveMotor motor1 = new ThrustCurveMotor( Manufacturer.getManufacturer("A"), - "F12X", "Desc", Motor.Type.UNKNOWN, new double[] { }, - 0.024, 0.07, new double[] { 0, 1, 2 }, new double[] {0, 1, 0}, - new Coordinate[] {Coordinate.NUL, Coordinate.NUL, Coordinate.NUL}, "digestA"); + "F12X", "Desc", Motor.Type.UNKNOWN, new double[] {}, + 0.024, 0.07, new double[] { 0, 1, 2 }, new double[] { 0, 1, 0 }, + new Coordinate[] { Coordinate.NUL, Coordinate.NUL, Coordinate.NUL }, "digestA"); private static final ThrustCurveMotor motor2 = new ThrustCurveMotor( Manufacturer.getManufacturer("A"), "F12H", "Desc", Motor.Type.SINGLE, new double[] { 5 }, - 0.024, 0.07, new double[] { 0, 1, 2 }, new double[] {0, 1, 0}, - new Coordinate[] {Coordinate.NUL, Coordinate.NUL, Coordinate.NUL}, "digestB"); + 0.024, 0.07, new double[] { 0, 1, 2 }, new double[] { 0, 1, 0 }, + new Coordinate[] { Coordinate.NUL, Coordinate.NUL, Coordinate.NUL }, "digestB"); private static final ThrustCurveMotor motor3 = new ThrustCurveMotor( Manufacturer.getManufacturer("A"), "F12", "Desc", Motor.Type.UNKNOWN, new double[] { 0, Motor.PLUGGED }, - 0.024, 0.07, new double[] { 0, 1, 2 }, new double[] {0, 2, 0}, - new Coordinate[] {Coordinate.NUL, Coordinate.NUL, Coordinate.NUL}, "digestC"); - + 0.024, 0.07, new double[] { 0, 1, 2 }, new double[] { 0, 2, 0 }, + new Coordinate[] { Coordinate.NUL, Coordinate.NUL, Coordinate.NUL }, "digestC"); + private static final ThrustCurveMotor motor4 = new ThrustCurveMotor( Manufacturer.getManufacturer("A"), "F12", "Desc", Motor.Type.HYBRID, new double[] { 0 }, - 0.024, 0.07, new double[] { 0, 1, 2 }, new double[] {0, 2, 0}, - new Coordinate[] {Coordinate.NUL, Coordinate.NUL, Coordinate.NUL}, "digestD"); + 0.024, 0.07, new double[] { 0, 1, 2 }, new double[] { 0, 2, 0 }, + new Coordinate[] { Coordinate.NUL, Coordinate.NUL, Coordinate.NUL }, "digestD"); @Test @@ -50,7 +54,7 @@ public class ThrustCurveMotorSetTest { assertEquals("J115", ThrustCurveMotorSet.simplifyDesignation("384-J115")); assertEquals("A2", ThrustCurveMotorSet.simplifyDesignation("A2T")); assertEquals("1/2A2T", ThrustCurveMotorSet.simplifyDesignation("1/2A2T")); - assertEquals("Micro Maxx II", ThrustCurveMotorSet.simplifyDesignation("Micro Maxx II")); + assertEquals("MicroMaxxII", ThrustCurveMotorSet.simplifyDesignation("Micro Maxx II")); } @Test diff --git a/core/test/net/sf/openrocket/plugin/Example2Plugin.java b/core/test/net/sf/openrocket/plugin/Example2Plugin.java new file mode 100644 index 000000000..a705139bd --- /dev/null +++ b/core/test/net/sf/openrocket/plugin/Example2Plugin.java @@ -0,0 +1,9 @@ +package net.sf.openrocket.plugin; + +/** + * Example plugin interface for testing purposes. + */ +@Plugin +public interface Example2Plugin { + +} diff --git a/core/src/net/sf/openrocket/plugin/ExamplePlugin.java b/core/test/net/sf/openrocket/plugin/ExamplePlugin.java similarity index 62% rename from core/src/net/sf/openrocket/plugin/ExamplePlugin.java rename to core/test/net/sf/openrocket/plugin/ExamplePlugin.java index b69950e80..a3e78481c 100644 --- a/core/src/net/sf/openrocket/plugin/ExamplePlugin.java +++ b/core/test/net/sf/openrocket/plugin/ExamplePlugin.java @@ -1,8 +1,9 @@ package net.sf.openrocket.plugin; +/** + * Example plugin for testing purposes. + */ @Plugin public interface ExamplePlugin { - public void doit(); - } diff --git a/core/src/net/sf/openrocket/plugin/ExamplePluginImpl.java b/core/test/net/sf/openrocket/plugin/ExamplePluginImpl.java similarity index 50% rename from core/src/net/sf/openrocket/plugin/ExamplePluginImpl.java rename to core/test/net/sf/openrocket/plugin/ExamplePluginImpl.java index 05a0aeb5b..b63d4df3f 100644 --- a/core/src/net/sf/openrocket/plugin/ExamplePluginImpl.java +++ b/core/test/net/sf/openrocket/plugin/ExamplePluginImpl.java @@ -1,10 +1,9 @@ package net.sf.openrocket.plugin; +/** + * ExamplePlugin implementation for testing purposes. + */ +@Plugin public class ExamplePluginImpl implements ExamplePlugin { - @Override - public void doit() { - System.out.println("ExamplePluginImpl.doit() called"); - } - } diff --git a/core/test/net/sf/openrocket/plugin/MultiPluginImpl.java b/core/test/net/sf/openrocket/plugin/MultiPluginImpl.java new file mode 100644 index 000000000..a48398536 --- /dev/null +++ b/core/test/net/sf/openrocket/plugin/MultiPluginImpl.java @@ -0,0 +1,10 @@ +package net.sf.openrocket.plugin; + +/** + * Plugin that implements both ExamplePlugin and Example2Plugin + * for testing purposes. + */ +@Plugin +public class MultiPluginImpl implements ExamplePlugin, Example2Plugin { + +} diff --git a/core/test/net/sf/openrocket/plugin/NotAnExamplePluginImpl.java b/core/test/net/sf/openrocket/plugin/NotAnExamplePluginImpl.java new file mode 100644 index 000000000..3f717389b --- /dev/null +++ b/core/test/net/sf/openrocket/plugin/NotAnExamplePluginImpl.java @@ -0,0 +1,9 @@ +package net.sf.openrocket.plugin; + +/** + * An implementation of ExamplePlugin that is not annotated with @Plugin + * for testing purposes. This should not be injected into the test class. + */ +public class NotAnExamplePluginImpl implements ExamplePlugin { + +} diff --git a/core/test/net/sf/openrocket/plugin/PluginTest.java b/core/test/net/sf/openrocket/plugin/PluginTest.java new file mode 100644 index 000000000..8e425b622 --- /dev/null +++ b/core/test/net/sf/openrocket/plugin/PluginTest.java @@ -0,0 +1,26 @@ +package net.sf.openrocket.plugin; + +import org.junit.Test; + +import com.google.inject.Guice; +import com.google.inject.Injector; + +/** + * Test the plugin loading system using Guice. + * + * This is more of an integration than a unit test. It uses the + * PluginModule to load a Guice injector, and then verifies that it + * has found the appropriate plugins. + */ +public class PluginTest { + + @Test + public void testPluginModule() { + + Injector injector = Guice.createInjector(new PluginModule()); + PluginTester tester = injector.getInstance(PluginTester.class); + tester.testPlugins(); + + } + +} diff --git a/core/test/net/sf/openrocket/plugin/PluginTester.java b/core/test/net/sf/openrocket/plugin/PluginTester.java new file mode 100644 index 000000000..87ee2f202 --- /dev/null +++ b/core/test/net/sf/openrocket/plugin/PluginTester.java @@ -0,0 +1,38 @@ +package net.sf.openrocket.plugin; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import java.util.Arrays; +import java.util.List; +import java.util.Set; + +import net.sf.openrocket.util.ArrayList; + +import com.google.inject.Inject; + +public class PluginTester { + + @Inject + private Set examplePlugins; + @Inject + private Set example2Plugins; + + + public void testPlugins() { + assertContains(examplePlugins, ExamplePluginImpl.class, MultiPluginImpl.class, JarPluginImpl.class); + assertContains(example2Plugins, MultiPluginImpl.class); + } + + + private void assertContains(Set set, Class... classes) { + assertEquals(classes.length, set.size()); + + List> list = new ArrayList>(Arrays.asList(classes)); + for (Object o : set) { + Class c = o.getClass(); + assertTrue(list.contains(c)); + list.remove(c); + } + } +} diff --git a/core/test/net/sf/openrocket/util/TestMutex.java b/core/test/net/sf/openrocket/util/TestMutex.java index 94d5a9f58..aa220a510 100644 --- a/core/test/net/sf/openrocket/util/TestMutex.java +++ b/core/test/net/sf/openrocket/util/TestMutex.java @@ -1,6 +1,10 @@ package net.sf.openrocket.util; -import static org.junit.Assert.*; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; import org.junit.Before; import org.junit.Test; @@ -53,7 +57,7 @@ public class TestMutex { } - + private volatile int testState = 0; private volatile String failure = null; @@ -106,6 +110,7 @@ public class TestMutex { testState = 6; } catch (Exception e) { + e.printStackTrace(); failure = "Exception occurred in thread: " + e; return; }