1 package net.sf.openrocket.preset;
3 import java.io.ByteArrayOutputStream;
4 import java.io.DataOutputStream;
5 import java.security.MessageDigest;
6 import java.util.ArrayList;
7 import java.util.Collections;
8 import java.util.Comparator;
9 import java.util.HashMap;
10 import java.util.List;
13 import net.sf.openrocket.material.Material;
14 import net.sf.openrocket.motor.Manufacturer;
15 import net.sf.openrocket.rocketcomponent.BodyTube;
16 import net.sf.openrocket.rocketcomponent.ExternalComponent.Finish;
17 import net.sf.openrocket.rocketcomponent.NoseCone;
18 import net.sf.openrocket.rocketcomponent.Transition.Shape;
19 import net.sf.openrocket.unit.UnitGroup;
20 import net.sf.openrocket.util.BugException;
21 import net.sf.openrocket.util.TextUtil;
25 * A model for a preset component.
27 * A preset component contains a component class type, manufacturer information,
28 * part information, and a method that returns a prototype of the preset component.
30 * @author Sampo Niskanen <sampo.niskanen@iki.fi>
32 // FIXME - Implement clone.
33 public class ComponentPreset implements Comparable<ComponentPreset> {
35 private final TypedPropertyMap properties = new TypedPropertyMap();
37 private boolean favorite = false;
38 private String digest = "";
41 BODY_TUBE( new TypedKey<?>[] {
42 ComponentPreset.MANUFACTURER,
43 ComponentPreset.PARTNO,
44 ComponentPreset.OUTER_DIAMETER,
45 ComponentPreset.INNER_DIAMETER,
46 ComponentPreset.LENGTH} ),
48 NOSE_CONE( new TypedKey<?>[] {
49 ComponentPreset.MANUFACTURER,
50 ComponentPreset.PARTNO,
51 ComponentPreset.SHAPE,
52 ComponentPreset.OUTER_DIAMETER,
53 ComponentPreset.LENGTH} ) ;
55 Type[] compatibleTypes;
56 TypedKey<?>[] displayedColumns;
58 Type( TypedKey<?>[] displayedColumns) {
59 compatibleTypes = new Type[1];
60 compatibleTypes[0] = this;
61 this.displayedColumns = displayedColumns;
64 Type( Type[] t, TypedKey<?>[] displayedColumns ) {
66 compatibleTypes = new Type[t.length+1];
67 compatibleTypes[0] = this;
68 for( int i=0; i<t.length; i++ ) {
69 compatibleTypes[i+1] = t[i];
72 this.displayedColumns = displayedColumns;
75 public Type[] getCompatibleTypes() {
76 return compatibleTypes;
79 public TypedKey<?>[] getDisplayedColumns() {
80 return displayedColumns;
85 public final static TypedKey<Manufacturer> MANUFACTURER = new TypedKey<Manufacturer>("Manufacturer", Manufacturer.class);
86 public final static TypedKey<String> PARTNO = new TypedKey<String>("PartNo",String.class);
87 public final static TypedKey<String> DESCRIPTION = new TypedKey<String>("Description", String.class);
88 public final static TypedKey<Type> TYPE = new TypedKey<Type>("Type",Type.class);
89 public final static TypedKey<Double> LENGTH = new TypedKey<Double>("Length", Double.class, UnitGroup.UNITS_LENGTH);
90 public final static TypedKey<Double> INNER_DIAMETER = new TypedKey<Double>("InnerDiameter", Double.class, UnitGroup.UNITS_LENGTH);
91 public final static TypedKey<Double> OUTER_DIAMETER = new TypedKey<Double>("OuterDiameter", Double.class, UnitGroup.UNITS_LENGTH);
92 public final static TypedKey<Double> SHOULDER_LENGTH = new TypedKey<Double>("ShoulderLength", Double.class, UnitGroup.UNITS_LENGTH);
93 public final static TypedKey<Double> SHOULDER_DIAMETER = new TypedKey<Double>("ShoulderDiameter", Double.class, UnitGroup.UNITS_LENGTH);
94 public final static TypedKey<Shape> SHAPE = new TypedKey<Shape>("Shape", Shape.class);
95 public final static TypedKey<Material> MATERIAL = new TypedKey<Material>("Material", Material.class);
96 public final static TypedKey<Finish> FINISH = new TypedKey<Finish>("Finish", Finish.class);
97 public final static TypedKey<Double> THICKNESS = new TypedKey<Double>("Thickness", Double.class, UnitGroup.UNITS_LENGTH);
98 public final static TypedKey<Boolean> FILLED = new TypedKey<Boolean>("Filled", Boolean.class);
99 public final static TypedKey<Double> MASS = new TypedKey<Double>("Mass", Double.class, UnitGroup.UNITS_MASS);
101 public final static Map<String, TypedKey<?>> keyMap = new HashMap<String, TypedKey<?>>();
103 keyMap.put(MANUFACTURER.getName(), MANUFACTURER);
104 keyMap.put(PARTNO.getName(), PARTNO);
105 keyMap.put(TYPE.getName(), TYPE);
106 keyMap.put(DESCRIPTION.getName(), DESCRIPTION);
107 keyMap.put(LENGTH.getName(), LENGTH);
108 keyMap.put(INNER_DIAMETER.getName(), INNER_DIAMETER);
109 keyMap.put(OUTER_DIAMETER.getName(), OUTER_DIAMETER);
110 keyMap.put(SHOULDER_LENGTH.getName(), SHOULDER_LENGTH);
111 keyMap.put(SHOULDER_DIAMETER.getName(), SHOULDER_DIAMETER);
112 keyMap.put(SHAPE.getName(), SHAPE);
113 keyMap.put(MATERIAL.getName(), MATERIAL);
114 keyMap.put(FINISH.getName(), FINISH);
115 keyMap.put(THICKNESS.getName(), THICKNESS);
116 keyMap.put(FILLED.getName(), FILLED);
117 keyMap.put(MASS.getName(), MASS);
120 public static ComponentPreset create( TypedPropertyMap props ) throws InvalidComponentPresetException {
122 ComponentPreset preset = new ComponentPreset();
123 // First do validation.
124 if ( !props.containsKey(TYPE)) {
125 throw new InvalidComponentPresetException("No Type specified " + props.toString() );
128 if (!props.containsKey(MANUFACTURER)) {
129 throw new InvalidComponentPresetException("No Manufacturer specified " + props.toString() );
132 if (!props.containsKey(PARTNO)) {
133 throw new InvalidComponentPresetException("No PartNo specified " + props.toString() );
136 preset.properties.putAll(props);
138 // Should check for various bits of each of the types.
139 Type t = props.get(TYPE);
143 if ( !props.containsKey(LENGTH) ) {
144 throw new InvalidComponentPresetException( "No Length specified for body tube preset " + props.toString());
147 BodyTube bt = new BodyTube();
149 bt.setLength(props.get(LENGTH));
151 // Need to verify contains 2 of OD, thickness, ID. Compute the third.
152 boolean hasOd = props.containsKey(OUTER_DIAMETER);
153 boolean hasId = props.containsKey(INNER_DIAMETER);
154 boolean hasThickness = props.containsKey(THICKNESS);
157 double outerRadius = props.get(OUTER_DIAMETER)/2.0;
158 double thickness = 0;
159 bt.setOuterRadius( outerRadius );
161 thickness = outerRadius - props.get(INNER_DIAMETER)/2.0;
162 } else if ( hasThickness ) {
163 thickness = props.get(THICKNESS);
165 throw new InvalidComponentPresetException("Body tube preset underspecified " + props.toString());
167 bt.setThickness( thickness );
169 if ( ! hasId && ! hasThickness ) {
170 throw new InvalidComponentPresetException("Body tube preset underspecified " + props.toString());
172 double innerRadius = props.get(INNER_DIAMETER)/2.0;
173 double thickness = props.get(THICKNESS);
174 bt.setOuterRadius(innerRadius + thickness);
175 bt.setThickness(thickness);
178 preset.properties.put(OUTER_DIAMETER, bt.getOuterRadius() *2.0);
179 preset.properties.put(INNER_DIAMETER, bt.getInnerRadius() *2.0);
180 preset.properties.put(THICKNESS, bt.getThickness());
182 // Need to translate Mass to Density.
183 if ( props.containsKey(MASS) ) {
184 String materialName = "TubeCustom";
185 if ( props.containsKey(MATERIAL) ) {
186 materialName = props.get(MATERIAL).getName();
188 Material m = Material.newMaterial(Material.Type.BULK, materialName, props.get(MASS)/bt.getComponentVolume(), false);
189 preset.properties.put(MATERIAL, m);
195 if ( !props.containsKey(LENGTH) ) {
196 throw new InvalidComponentPresetException( "No Length specified for nose cone preset " + props.toString());
198 if ( !props.containsKey(SHAPE) ) {
199 throw new InvalidComponentPresetException( "No Shape specified for nose cone preset " + props.toString());
201 if ( !props.containsKey(OUTER_DIAMETER) ) {
202 throw new InvalidComponentPresetException( "No Outer Diameter specified for nose cone preset " + props.toString());
205 if ( props.containsKey(MASS) ) {
206 // compute a density for this component
207 double mass = props.get(MASS);
208 NoseCone nc = new NoseCone();
209 nc.loadPreset(preset);
210 double density = mass / nc.getComponentVolume();
212 String materialName = "NoseConeCustom";
213 if ( props.containsKey(MATERIAL) ) {
214 materialName = props.get(MATERIAL).getName();
217 Material m = Material.newMaterial(Material.Type.BULK, materialName,density, false);
218 preset.properties.put(MATERIAL, m);
225 preset.computeDigest();
231 // Private constructor to encourage use of factory.
232 private ComponentPreset() {
236 * Convenience method to retrieve the Type of this ComponentPreset.
240 public Type getType() {
241 return properties.get(TYPE);
245 * Convenience method to retrieve the Manufacturer of this ComponentPreset.
248 public Manufacturer getManufacturer() {
249 return properties.get(MANUFACTURER);
253 * Convenience method to retrieve the PartNo of this ComponentPreset.
256 public String getPartNo() {
257 return properties.get(PARTNO);
260 public String getDigest() {
264 public boolean has(Object key) {
265 return properties.containsKey(key);
268 public <T> T get(TypedKey<T> key) {
269 T value = properties.get(key);
271 throw new BugException("Preset did not contain key " + key + " " + properties.toString());
276 public boolean isFavorite() {
280 public void setFavorite(boolean favorite) {
281 this.favorite = favorite;
285 public int compareTo(ComponentPreset p2) {
286 int manuCompare = this.getManufacturer().getSimpleName().compareTo(p2.getManufacturer().getSimpleName());
287 if ( manuCompare != 0 )
290 int partNoCompare = this.getPartNo().compareTo(p2.getPartNo());
291 return partNoCompare;
295 public String toString() {
296 return get(MANUFACTURER).toString() + " " + get(PARTNO);
299 public String preferenceKey() {
300 return get(MANUFACTURER).toString() + "|" + get(PARTNO);
303 private void computeDigest() {
306 ByteArrayOutputStream bos = new ByteArrayOutputStream();
307 DataOutputStream os = new DataOutputStream(bos);
309 List<TypedKey<?>> keys = new ArrayList<TypedKey<?>>( properties.keySet());
311 Collections.sort(keys, new Comparator<TypedKey<?>>() {
313 public int compare( TypedKey<?> a, TypedKey<?> b ) {
314 return a.getName().compareTo(b.getName());
318 for ( TypedKey<?> key : keys ) {
320 Object value = properties.get(key);
322 os.writeBytes(key.getName());
324 if ( key.getType() == Double.class ) {
325 Double d = (Double) value;
327 } else if (key.getType() == String.class ) {
328 String s = (String) value;
330 } else if (key.getType() == Manufacturer.class ) {
331 String s = ((Manufacturer)value).getSimpleName();
333 } else if ( key.getType() == Finish.class ) {
334 String s = ((Finish)value).name();
336 } else if ( key.getType() == Type.class ) {
337 String s = ((Type)value).name();
339 } else if ( key.getType() == Boolean.class ) {
340 Boolean b = (Boolean) value;
342 } else if ( key.getType() == Material.class ) {
343 double d = ((Material)value).getDensity();
345 } else if ( key.getType() == Shape.class ) {
346 // FIXME - this is ugly to use the ordinal but what else?
347 int i = ((Shape)value).ordinal();
353 MessageDigest md5 = MessageDigest.getInstance("MD5");
354 digest = TextUtil.hexString(md5.digest( bos.toByteArray() ));
356 catch ( Exception e ) {
357 throw new BugException(e);