DGP - added DensityType parsing for recovery devices
[debian/openrocket] / src / net / sf / openrocket / file / rocksim / BaseHandler.java
1 /*
2  * BaseHandler.java
3  */
4 package net.sf.openrocket.file.rocksim;
5
6 import net.sf.openrocket.aerodynamics.WarningSet;
7 import net.sf.openrocket.file.simplesax.ElementHandler;
8 import net.sf.openrocket.material.Material;
9 import net.sf.openrocket.rocketcomponent.RocketComponent;
10 import org.xml.sax.SAXException;
11
12 import java.lang.reflect.InvocationTargetException;
13 import java.lang.reflect.Method;
14 import java.util.HashMap;
15
16 /**
17  * An abstract base class that handles common parsing.  All Rocksim component handlers are subclassed from here.
18  *
19  * @param <C>   the specific RocketComponent subtype for which the concrete handler can create
20  */
21 public abstract class BaseHandler<C extends RocketComponent> extends ElementHandler {
22
23     /**
24      * The overridden mass.
25      */
26     private Double mass = 0d;
27     /**
28      * The overridden Cg.
29      */
30     private Double cg = 0d;
31     /**
32      * The density of the material in the component.
33      */
34     private Double density = 0d;
35     /**
36      * The internal Rocksim density type.
37      */
38     private RocksimDensityType densityType = RocksimDensityType.ROCKSIM_BULK;
39
40     /**
41      * The material name.
42      */
43     private String materialName = "";
44
45     /**
46      * The SAX method called when the closing element tag is reached.
47      *
48      * @param element        the element name.
49      * @param attributes    attributes of the element.
50      * @param content        the textual content of the element.
51      * @param warnings        the warning set to store warnings in.
52      * @throws SAXException
53      */
54
55     @Override
56     public void closeElement(String element, HashMap<String, String> attributes, String content, WarningSet warnings)
57             throws SAXException {
58         final C component = getComponent();
59         try {
60             if ("Name".equals(element)) {
61                 component.setName(content);
62             }
63             if ("KnownMass".equals(element)) {
64                 mass = Math.max(0d, Double.parseDouble(content) / RocksimHandler.ROCKSIM_TO_OPENROCKET_MASS);
65             }
66             if ("Density".equals(element)) {
67                 density = Math.max(0d, Double.parseDouble(content) );
68             }
69             if ("KnownCG".equals(element)) {
70                 cg = Math.max(0d, Double.parseDouble(content) / RocksimHandler.ROCKSIM_TO_OPENROCKET_LENGTH);
71             }
72             if ("UseKnownCG".equals(element)) {  //Rocksim sets UseKnownCG to true to control the override of both cg and mass
73                 boolean override = "1".equals(content);
74                 setOverride(component, override, mass, cg);
75             }
76             if ("DensityType".equals(element)) {
77                 densityType = RocksimDensityType.fromCode(Integer.parseInt(content));
78             }
79         }
80         catch (NumberFormatException nfe) {
81             warnings.add("Could not convert " + element + " value of " + content + ".  It is expected to be a number.");
82         }
83     }
84
85     /**
86      * {@inheritDoc}
87      */
88     @Override
89     public void endHandler(String element, HashMap<String, String> attributes, String content, WarningSet warnings)
90             throws SAXException {
91         /* Because of the order of XML elements in Rocksim, not all information is known at the time it really needs
92            to be acted upon.  So we keep temporary instance variables to be used here at the end of the parsing.
93          */
94         density = computeDensity(densityType, density);
95         RocketComponent component = getComponent();
96         updateComponentMaterial(component, materialName, getMaterialType(), density);
97     }
98
99     /**
100      * Compute the density.  Rocksim does strange things with densities.  For some streamer material it's in cubic,
101      * rather than square, units.  In those cases it needs to be converted to an appropriate SURFACE material density.
102      * Some G10 fiberglass materials are in cubic units, other G10 fiberglass is in square units.  And due to a
103      * Rocksim bug, some densities are 0 when they clearly should not be.
104      *
105      * This may be overridden for specific component density computations.
106      *
107      * @param type       the rocksim density
108      * @param rawDensity the density as specified in the Rocksim design file
109      * @return a value in OpenRocket SURFACE density units
110      */
111     protected double computeDensity(RocksimDensityType type, double rawDensity) {
112         return rawDensity / type.asOpenRocket();
113     }
114
115     /**
116      * If the Rocksim component does not override the mass, then create a Material based upon the density defined
117      * for that component.  This *should* result in a consistent representation of Cg between Rocksim and OpenRocket.
118      *
119      * @param component       the component
120      * @param type            the type of the material
121      * @param density         the density in g/cm^3
122      * @param definedMaterial the material that is currently defined on the component; used only to get the name
123      *                        as it appears in Rocksim
124      */
125     public static void updateComponentMaterial(RocketComponent component, String definedMaterial, Material.Type type,
126                                                double density) {
127         if (definedMaterial != null) {
128             Material custom = createCustomMaterial(type, definedMaterial, density);
129             setMaterial(component, custom);
130         }
131     }
132
133     /**
134      * Override the mass and Cg of the component.
135      *
136      * @param component  the component
137      * @param override   true if any override should happen
138      * @param mass       the override mass
139      * @param cg         the override cg
140      */
141     public static void setOverride(RocketComponent component, boolean override, double mass, double cg) {
142         if (override) {
143             component.setCGOverridden(override);
144             component.setMassOverridden(override);
145             component.setOverrideSubcomponents(false); //Rocksim does not support this type of override
146             component.setOverrideMass(mass);
147             component.setOverrideCGX(cg);
148         }
149     }
150
151     /**
152      * Get the component this handler is working upon.
153      *
154      * @return a component
155      */
156     protected abstract C getComponent();
157
158     /**
159      * Get the required type of material for this component.
160      *
161      * @return the required material type
162      */
163     protected abstract Material.Type getMaterialType();
164
165     /**
166      * Some CG positions in Rocksim do not correspond to the CG position reference in OpenRocket.
167      *
168      * @param theCG  the CG value to really use when overriding CG on the OpenRocket component
169      */
170     protected void setCG(double theCG) {
171         cg = theCG;
172     }
173
174     /**
175      * Set the material name as specified in the Rocksim design file.
176      *
177      * @param content  the material name
178      */
179     protected void setMaterialName(String content) {
180         materialName = content;
181     }
182
183     /**
184      * Create a custom material based on the density.
185      *
186      * @param type    the type of the material
187      * @param name    the name of the component
188      * @param density the density in g/cm^3
189      *
190      * @return a Material instance
191      */
192     public static Material createCustomMaterial(Material.Type type, String name, double density) {
193         return Material.newMaterial(type, "RS: " + name, density, true);
194     }
195
196     /**
197      * Set the material onto an instance of RocketComponent.  This is done because only some subtypes of RocketComponent
198      * have the setMaterial method.  Unfortunately the supertype cannot be used.
199      *
200      * @param component  the component who's material is to be set
201      * @param material the material to be set on the component (defined by getComponent())
202      */
203     private static void setMaterial(RocketComponent component, Material material) {
204         try {
205             final Method method = getMethod(component, "setMaterial", new Class[]{Material.class});
206             if (method != null) {
207                 method.invoke(component, material);
208             }
209         }
210         catch (IllegalAccessException ignored) {
211         }
212         catch (InvocationTargetException ignored) {
213         }
214     }
215
216     /**
217      * Find a method by name and argument list.
218      *
219      * @param component  the component who's material is to be seta
220      * @param name the method name
221      * @param args the class types of the parameters
222      *
223      * @return the Method instance, or null
224      */
225     private static Method getMethod(RocketComponent component, String name, Class[] args) {
226         Method method = null;
227         try {
228             method = component.getClass().getMethod(name, args);
229         }
230         catch (NoSuchMethodException ignored) {
231         }
232         return method;
233     }
234
235 }