6302c62a3e19baa9a3ea917764c8f3ef9b37ba32
[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      * @throws SAXException
59      */
60
61     @Override
62     public void closeElement(String element, HashMap<String, String> attributes, String content, WarningSet warnings)
63             throws SAXException {
64         final C component = getComponent();
65         try {
66             if (RocksimCommonConstants.NAME.equals(element)) {
67                 component.setName(content);
68             }
69             if (RocksimCommonConstants.KNOWN_MASS.equals(element)) {
70                 mass = Math.max(0d, Double.parseDouble(content) / RocksimCommonConstants.ROCKSIM_TO_OPENROCKET_MASS);
71             }
72             if (RocksimCommonConstants.DENSITY.equals(element)) {
73                 density = Math.max(0d, Double.parseDouble(content) );
74             }
75             if (RocksimCommonConstants.KNOWN_CG.equals(element)) {
76                 cg = Math.max(0d, Double.parseDouble(content) / RocksimCommonConstants.ROCKSIM_TO_OPENROCKET_LENGTH);
77             }
78             if (RocksimCommonConstants.USE_KNOWN_CG.equals(element)) {  //Rocksim sets UseKnownCG to true to control the override of both cg and mass
79                 boolean override = "1".equals(content);
80                 setOverride(component, override, mass, cg);
81             }
82             if (RocksimCommonConstants.DENSITY_TYPE.equals(element)) {
83                 densityType = RocksimDensityType.fromCode(Integer.parseInt(content));
84             }
85         }
86         catch (NumberFormatException nfe) {
87             warnings.add("Could not convert " + element + " value of " + content + ".  It is expected to be a number.");
88         }
89     }
90
91     /**
92      * {@inheritDoc}
93      */
94     @Override
95     public void endHandler(String element, HashMap<String, String> attributes, String content, WarningSet warnings)
96             throws SAXException {
97         /* Because of the order of XML elements in Rocksim, not all information is known at the time it really needs
98            to be acted upon.  So we keep temporary instance variables to be used here at the end of the parsing.
99          */
100         density = computeDensity(densityType, density);
101         RocketComponent component = getComponent();
102         updateComponentMaterial(component, materialName, getMaterialType(), density);
103     }
104
105     /**
106      * Compute the density.  Rocksim does strange things with densities.  For some streamer material it's in cubic,
107      * rather than square, units.  In those cases it needs to be converted to an appropriate SURFACE material density.
108      * Some G10 fiberglass materials are in cubic units, other G10 fiberglass is in square units.  And due to a
109      * Rocksim bug, some densities are 0 when they clearly should not be.
110      *
111      * This may be overridden for specific component density computations.
112      *
113      * @param type       the rocksim density
114      * @param rawDensity the density as specified in the Rocksim design file
115      * @return a value in OpenRocket SURFACE density units
116      */
117     protected double computeDensity(RocksimDensityType type, double rawDensity) {
118         return rawDensity / type.asOpenRocket();
119     }
120
121     /**
122      * If the Rocksim component does not override the mass, then create a Material based upon the density defined
123      * for that component.  This *should* result in a consistent representation of Cg between Rocksim and OpenRocket.
124      *
125      * @param component       the component
126      * @param type            the type of the material
127      * @param density         the density in g/cm^3
128      * @param definedMaterial the material that is currently defined on the component; used only to get the name
129      *                        as it appears in Rocksim
130      */
131     public static void updateComponentMaterial(RocketComponent component, String definedMaterial, Material.Type type,
132                                                double density) {
133         if (definedMaterial != null) {
134             Material custom = createCustomMaterial(type, definedMaterial, density);
135             setMaterial(component, custom);
136         }
137     }
138
139     /**
140      * Override the mass and Cg of the component.
141      *
142      * @param component  the component
143      * @param override   true if any override should happen
144      * @param mass       the override mass
145      * @param cg         the override cg
146      */
147     public static void setOverride(RocketComponent component, boolean override, double mass, double cg) {
148         if (override) {
149             component.setCGOverridden(override);
150             component.setMassOverridden(override);
151             component.setOverrideSubcomponents(false); //Rocksim does not support this type of override
152             component.setOverrideMass(mass);
153             component.setOverrideCGX(cg);
154         }
155     }
156
157     /**
158      * Get the component this handler is working upon.
159      *
160      * @return a component
161      */
162     protected abstract C getComponent();
163
164     /**
165      * Get the required type of material for this component.
166      *
167      * @return the required material type
168      */
169     protected abstract Material.Type getMaterialType();
170
171     /**
172      * Some CG positions in Rocksim do not correspond to the CG position reference in OpenRocket.
173      *
174      * @param theCG  the CG value to really use when overriding CG on the OpenRocket component
175      */
176     protected void setCG(double theCG) {
177         cg = theCG;
178     }
179
180     /**
181      * Set the material name as specified in the Rocksim design file.
182      *
183      * @param content  the material name
184      */
185     protected void setMaterialName(String content) {
186         materialName = content;
187     }
188
189     /**
190      * Add child to parent only if the child is compatible.  Otherwise add to warning set.
191      * 
192      * @param parent  the parent component
193      * @param child   the child component
194      * @param warnings the warning set
195      * 
196      * @return true if the child is compatible with parent
197      */
198     protected static boolean isCompatible(RocketComponent parent, Class<? extends RocketComponent> child, WarningSet warnings) {
199         if (!parent.isCompatible(child)) {
200             warnings.add(child.getName() + " can not be attached to "
201                          + parent.getComponentName() + ", ignoring component.");
202             return false;
203         }
204         else {
205             return true;
206         }
207     }
208     
209     /**
210      * Create a custom material based on the density.  The name of the material is prepended with 'RS: ' to
211      * indicate it came from a RockSim material.
212      *
213      * @param type    the type of the material
214      * @param name    the name of the component
215      * @param density the density
216      *
217      * @return a Material instance
218      */
219     public static Material createCustomMaterial(Material.Type type, String name, double density) {
220         return Material.newMaterial(type, ROCKSIM_MATERIAL_PREFIX + name, density, true);
221     }
222
223     /**
224      * Set the material onto an instance of RocketComponent.  This is done because only some subtypes of RocketComponent
225      * have the setMaterial method.  Unfortunately the supertype cannot be used.
226      *
227      * @param component  the component who's material is to be set
228      * @param material the material to be set on the component (defined by getComponent())
229      */
230     private static void setMaterial(RocketComponent component, Material material) {
231         try {
232             final Method method = getMethod(component, "setMaterial", new Class[]{Material.class});
233             if (method != null) {
234                 method.invoke(component, material);
235             }
236         }
237         catch (IllegalAccessException ignored) {
238         }
239         catch (InvocationTargetException ignored) {
240         }
241     }
242
243     /**
244      * Find a method by name and argument list.
245      *
246      * @param component  the component who's material is to be seta
247      * @param name the method name
248      * @param args the class types of the parameters
249      *
250      * @return the Method instance, or null
251      */
252     private static Method getMethod(RocketComponent component, String name, Class[] args) {
253         Method method = null;
254         try {
255             method = component.getClass().getMethod(name, args);
256         }
257         catch (NoSuchMethodException ignored) {
258         }
259         return method;
260     }
261
262 }