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