create changelog entry
[debian/openrocket] / core / src / net / sf / openrocket / file / rocksim / importt / MassObjectHandler.java
1 /*
2  * MassObjectHandler.java
3  */
4 package net.sf.openrocket.file.rocksim.importt;
5
6 import net.sf.openrocket.aerodynamics.WarningSet;
7 import net.sf.openrocket.file.rocksim.RocksimCommonConstants;
8 import net.sf.openrocket.file.rocksim.RocksimDensityType;
9 import net.sf.openrocket.file.simplesax.ElementHandler;
10 import net.sf.openrocket.file.simplesax.PlainTextHandler;
11 import net.sf.openrocket.material.Material;
12 import net.sf.openrocket.rocketcomponent.Coaxial;
13 import net.sf.openrocket.rocketcomponent.MassComponent;
14 import net.sf.openrocket.rocketcomponent.MassObject;
15 import net.sf.openrocket.rocketcomponent.RocketComponent;
16 import net.sf.openrocket.rocketcomponent.ShockCord;
17 import org.xml.sax.SAXException;
18
19 import java.util.HashMap;
20
21 /**
22  * A SAX handler for Rocksim's MassObject XML type.
23  */
24 class MassObjectHandler extends PositionDependentHandler<MassObject> {
25
26     /**
27      * The Rocksim Mass length fudge factor.  Rocksim completely exaggerates the length of a mass object to the point
28      * that it looks ridiculous in OpenRocket.  This fudge factor is here merely to get the typical mass object to
29      * render in the OpenRocket UI with it's bounds mostly inside it's parent.  The odd thing about it is that Rocksim
30      * does not expose the length of a mass object in the UI and actually treats mass objects as point objects - not 3
31      * or even 2 dimensional.
32      */
33     public static final int MASS_LEN_FUDGE_FACTOR = 100;
34
35     /**
36      * The OpenRocket MassComponent - counterpart to the RS MassObject.
37      */
38     private final MassComponent mass;
39
40     /**
41      * Reference to answer for getComponent().
42      */
43     private MassObject current;
44
45     /**
46      * Parent.
47      */
48     private RocketComponent parent;
49
50     /**
51      * 0 == General, 1 == Shock Cord
52      */
53     private int typeCode = 0;
54
55     /**
56      * Constructor. l
57      *
58      * @param c        the parent component
59      * @param warnings the warning set
60      *
61      * @throws IllegalArgumentException thrown if <code>c</code> is null
62      */
63     public MassObjectHandler(RocketComponent c, WarningSet warnings) throws IllegalArgumentException {
64         if (c == null) {
65             throw new IllegalArgumentException("The parent component of a mass component may not be null.");
66         }
67         mass = new MassComponent();
68         current = mass;
69         parent = c;
70     }
71
72     @Override
73     public ElementHandler openElement(String element, HashMap<String, String> attributes, WarningSet warnings) {
74         return PlainTextHandler.INSTANCE;
75     }
76
77     @Override
78     public void closeElement(String element, HashMap<String, String> attributes, String content, WarningSet warnings)
79             throws SAXException {
80         super.closeElement(element, attributes, content, warnings);
81         try {
82             if (RocksimCommonConstants.LEN.equals(element)) {
83                 mass.setLength(Double.parseDouble(content) / (RocksimCommonConstants.ROCKSIM_TO_OPENROCKET_LENGTH));
84             }
85             if (RocksimCommonConstants.KNOWN_MASS.equals(element)) {
86                 mass.setComponentMass(Double.parseDouble(content) / RocksimCommonConstants.ROCKSIM_TO_OPENROCKET_MASS);
87             }
88             if (RocksimCommonConstants.KNOWN_CG.equals(element)) {
89                 //Setting the CG of the Mass Object to 0 is important because of the different ways that Rocksim and
90                 //OpenRocket treat mass objects.  Rocksim treats them as points (even though the data file contains a
91                 //length) and because Rocksim sets the CG of the mass object to really be relative to the front of
92                 //the parent.  But that value is already assumed in the position and position value for the component.
93                 //Thus it needs to be set to 0 to say that the mass object's CG is at the point of the mass object.
94                 super.setCG(0);
95             }
96             if (RocksimCommonConstants.TYPE_CODE.equals(element)) {
97                 typeCode = Integer.parseInt(content);
98             }
99             if (RocksimCommonConstants.MATERIAL.equals(element)) {
100                 setMaterialName(content);
101             }
102         }
103         catch (NumberFormatException nfe) {
104             warnings.add("Could not convert " + element + " value of " + content + ".  It is expected to be a number.");
105         }
106     }
107
108     @Override
109     public void endHandler(String element, HashMap<String, String> attributes, String content, WarningSet warnings) throws
110                                                                                                                     SAXException {
111         if (inferAsShockCord(typeCode, warnings)) { //Shock Cord
112             mapMassObjectAsShockCord(element, attributes, content, warnings);
113         }
114         else { // typeCode == 0   General Mass Object
115             if (isCompatible(parent, MassComponent.class, warnings)) {
116                 parent.addChild(mass);
117             }
118             super.endHandler(element, attributes, content, warnings);
119         }
120     }
121
122     /**
123      * Rocksim does not have a separate entity for Shock Cords.  It has to be inferred.  Sometimes the typeCode
124      * indicates it's a shock cord, but most times it does not.  This is due to bugs in the Rocksim Component and
125      * Material databases.  Try to infer a shock cord based on it's length and it's material type.  It's somewhat
126      * arbitrary, but if the mass object's length is more than twice the length of it's parent component and it's a LINE
127      * material, then assume a shock cord.
128      *
129      * @param theTypeCode the code from the RKT XML file
130      *
131      * @return true if we think it's a shock cord
132      */
133     private boolean inferAsShockCord(int theTypeCode, WarningSet warnings) {
134         return (theTypeCode == 1 || (mass.getLength() >= 2 * parent.getLength() && RocksimDensityType.ROCKSIM_LINE
135                 .equals(getDensityType()))) && isCompatible(parent, ShockCord.class, warnings, true);
136     }
137
138     /**
139      * If it appears that the mass object is a shock cord, then create an OR shock cord instance.
140      *
141      * @param element    the element name
142      * @param attributes the attributes
143      * @param content    the content of the element
144      * @param warnings   the warning set to store warnings in.
145      *
146      * @throws org.xml.sax.SAXException not thrown
147      */
148     private void mapMassObjectAsShockCord(final String element, final HashMap<String, String> attributes,
149                                           final String content, final WarningSet warnings) throws SAXException {
150         ShockCord cord = new ShockCord();
151         current = cord;
152         if (isCompatible(parent, ShockCord.class, warnings)) {
153             parent.addChild(cord);
154         }
155         super.endHandler(element, attributes, content, warnings);
156         cord.setName(mass.getName());
157
158         setOverride(cord, mass.isMassOverridden(), mass.getOverrideMass(), mass.getOverrideCGX());
159
160         cord.setRadialDirection(mass.getRadialDirection());
161         cord.setRadialPosition(mass.getRadialPosition());
162         cord.setRadius(mass.getRadius());
163
164         //Rocksim does not distinguish between total length of the cord and the packed length.  Fudge the
165         //packed length and set the real length.
166         cord.setCordLength(mass.getLength());
167         cord.setLength(cord.getCordLength() / MASS_LEN_FUDGE_FACTOR);
168         if (parent instanceof Coaxial) {
169             Coaxial parentCoaxial = (Coaxial) parent;
170             cord.setRadius(parentCoaxial.getInnerRadius());
171         }
172     }
173
174     /**
175      * Get the component this handler is working upon.  This changes depending upon the type of mass object.
176      *
177      * @return a component
178      */
179     @Override
180     public MassObject getComponent() {
181         return current;
182     }
183
184     /**
185      * Set the relative position onto the component.  This cannot be done directly because setRelativePosition is not
186      * public in all components.
187      *
188      * @param position the OpenRocket position
189      */
190     public void setRelativePosition(RocketComponent.Position position) {
191         current.setRelativePosition(position);
192     }
193
194     /**
195      * Get the required type of material for this component.  Does not apply to MassComponents, but does apply to Shock
196      * Cords.
197      *
198      * @return LINE
199      */
200     @Override
201     public Material.Type getMaterialType() {
202         return Material.Type.LINE;
203     }
204
205 }