DGP - Fix to non-bulk densities
[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 public abstract class BaseHandler<C extends RocketComponent> extends ElementHandler {
20
21     /**
22      * The overridden mass.
23      */
24     private Double mass = 0d;
25     /**
26      * The overridden Cg.
27      */
28     private Double cg = 0d;
29     /**
30      * The density of the material in the component.
31      */
32     private Double density = 0d;
33     /**
34      * The material name.
35      */
36     private String materialName = "";
37
38     /**
39      * The SAX method called when the closing element tag is reached.
40      *
41      * @param element        the element name.
42      * @param attributes    attributes of the element.
43      * @param content        the textual content of the element.
44      * @param warnings        the warning set to store warnings in.
45      * @throws SAXException
46      */
47
48     @Override
49     public void closeElement(String element, HashMap<String, String> attributes, String content, WarningSet warnings)
50             throws SAXException {
51         final C component = getComponent();
52         try {
53             if ("Name".equals(element)) {
54                 component.setName(content);
55             }
56             if ("KnownMass".equals(element)) {
57                 mass = Math.max(0d, Double.parseDouble(content) / RocksimHandler.ROCKSIM_TO_OPENROCKET_MASS);
58             }
59             if ("Density".equals(element)) {
60                 density = Math.max(0d, Double.parseDouble(content) / getDensityConversion());
61             }
62             if ("KnownCG".equals(element)) {
63                 cg = Math.max(0d, Double.parseDouble(content) / RocksimHandler.ROCKSIM_TO_OPENROCKET_LENGTH);
64             }
65             if ("UseKnownCG".equals(element)) {
66                 boolean override = "1".equals(content);
67                 setOverride(component, override, mass, cg);
68             }
69         }
70         catch (NumberFormatException nfe) {
71             warnings.add("Could not convert " + element + " value of " + content + ".  It is expected to be a number.");
72         }
73     }
74
75     @Override
76     public void endHandler(String element, HashMap<String, String> attributes, String content, WarningSet warnings)
77             throws SAXException {
78         /* Because of the order of XML elements in Rocksim, not all information is known at the time it really needs
79            to be acted upon.  So we keep temporary instance variables to be used here at the end of the parsing.
80          */
81         RocketComponent component = getComponent();
82         updateComponentMaterial(component, materialName, getMaterialType(), density);
83     }
84
85     /**
86      * If the Rocksim component does not override the mass, then create a Material based upon the density defined
87      * for that component.  This *should* result in a consistent representation of Cg between Rocksim and OpenRocket.
88      *
89      * @param component       the component
90      * @param type            the type of the material
91      * @param density         the density in g/cm^3
92      * @param definedMaterial the material that is currently defined on the component; used only to get the name
93      *                        as it appears in Rocksim
94      */
95     public static void updateComponentMaterial(RocketComponent component, String definedMaterial, Material.Type type,
96                                                double density) {
97         if (definedMaterial != null) {
98             Material custom = createCustomMaterial(type, definedMaterial, density);
99             setMaterial(component, custom);
100         }
101     }
102
103     /**
104      * Override the mass and Cg of the component.
105      *
106      * @param component  the component
107      * @param override   true if any override should happen
108      * @param mass       the override mass
109      * @param cg         the override cg
110      */
111     public static void setOverride(RocketComponent component, boolean override, double mass, double cg) {
112         if (override) {
113             component.setCGOverridden(override);
114             component.setMassOverridden(override);
115             component.setOverrideSubcomponents(false); //Rocksim does not support this type of override
116             component.setOverrideMass(mass);
117             component.setOverrideCGX(cg);
118         }
119     }
120
121     /**
122      * Get the component this handler is working upon.
123      *
124      * @return a component
125      */
126     protected abstract C getComponent();
127
128     /**
129      * Get the required type of material for this component.
130      *
131      * @return the required material type
132      */
133     protected abstract Material.Type getMaterialType();
134
135     /**
136      * Some CG positions in Rocksim do not correspond to the CG position reference in OpenRocket.
137      *
138      * @param theCG  the CG value to really use when overriding CG on the OpenRocket component
139      */
140     protected void setCG(double theCG) {
141         cg = theCG;
142     }
143
144     /**
145      * Set the material name as specified in the Rocksim design file.
146      *
147      * @param content  the material name
148      */
149     protected void setMaterialName(String content) {
150         materialName = content;
151     }
152
153     /**
154      * Create a custom material based on the density.
155      *
156      * @param type    the type of the material
157      * @param name    the name of the component
158      * @param density the density in g/cm^3
159      *
160      * @return a Material instance
161      */
162     public static Material createCustomMaterial(Material.Type type, String name, double density) {
163         return Material.newMaterial(type, "RS: " + name, density, true);
164     }
165
166     /**
167      * Get the appropriate density conversion for different types of materials.
168      *
169      * @return a conversion value that is assumed to be in Rocksim Units / OpenRocket Units
170      */
171     private double getDensityConversion() {
172         switch (getMaterialType()) {
173             case LINE:
174                 return RocksimHandler.ROCKSIM_TO_OPENROCKET_LINE_DENSITY;
175             case SURFACE:
176                 return RocksimHandler.ROCKSIM_TO_OPENROCKET_SURFACE_DENSITY;
177             case BULK:
178             default:
179                 return RocksimHandler.ROCKSIM_TO_OPENROCKET_BULK_DENSITY;
180         }
181     }
182
183     /**
184      * Set the material onto an instance of RocketComponent.  This is done because only some subtypes of RocketComponent
185      * have the setMaterial method.  Unfortunately the supertype cannot be used.
186      *
187      * @param component  the component who's material is to be set
188      * @param material the material to be set on the component (defined by getComponent())
189      */
190     private static void setMaterial(RocketComponent component, Material material) {
191         try {
192             final Method method = getMethod(component, "setMaterial", new Class[]{Material.class});
193             if (method != null) {
194                 method.invoke(component, material);
195             }
196         }
197         catch (IllegalAccessException ignored) {
198         }
199         catch (InvocationTargetException ignored) {
200         }
201     }
202
203     /**
204      * Find a method by name and argument list.
205      *
206      * @param component  the component who's material is to be seta
207      * @param name the method name
208      * @param args the class types of the parameters
209      *
210      * @return the Method instance, or null
211      */
212     private static Method getMethod(RocketComponent component, String name, Class[] args) {
213         Method method = null;
214         try {
215             method = component.getClass().getMethod(name, args);
216         }
217         catch (NoSuchMethodException ignored) {
218         }
219         return method;
220     }
221
222 }