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