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;
19 import net.sf.openrocket.rocketcomponent.Transition.Shape;
20 import net.sf.openrocket.unit.UnitGroup;
21 import net.sf.openrocket.util.BugException;
22 import net.sf.openrocket.util.TextUtil;
26 * A model for a preset component.
28 * A preset component contains a component class type, manufacturer information,
29 * part information, and a method that returns a prototype of the preset component.
31 * @author Sampo Niskanen <sampo.niskanen@iki.fi>
33 // FIXME - Implement clone.
34 public class ComponentPreset implements Comparable<ComponentPreset> {
36 private final TypedPropertyMap properties = new TypedPropertyMap();
38 private boolean favorite = false;
39 private String digest = "";
42 BODY_TUBE( new TypedKey<?>[] {
43 ComponentPreset.MANUFACTURER,
44 ComponentPreset.PARTNO,
45 ComponentPreset.OUTER_DIAMETER,
46 ComponentPreset.INNER_DIAMETER,
47 ComponentPreset.LENGTH} ),
49 NOSE_CONE( new TypedKey<?>[] {
50 ComponentPreset.MANUFACTURER,
51 ComponentPreset.PARTNO,
52 ComponentPreset.DESCRIPTION,
53 ComponentPreset.SHAPE,
54 ComponentPreset.OUTER_DIAMETER,
55 ComponentPreset.LENGTH} ),
57 TRANSITION( new TypedKey<?>[] {
58 ComponentPreset.MANUFACTURER,
59 ComponentPreset.PARTNO,
60 ComponentPreset.DESCRIPTION,
61 ComponentPreset.SHAPE,
62 ComponentPreset.FORE_OUTER_DIAMETER,
63 ComponentPreset.OUTER_DIAMETER,
64 ComponentPreset.LENGTH
67 Type[] compatibleTypes;
68 TypedKey<?>[] displayedColumns;
70 Type( TypedKey<?>[] displayedColumns) {
71 compatibleTypes = new Type[1];
72 compatibleTypes[0] = this;
73 this.displayedColumns = displayedColumns;
76 Type( Type[] t, TypedKey<?>[] displayedColumns ) {
78 compatibleTypes = new Type[t.length+1];
79 compatibleTypes[0] = this;
80 for( int i=0; i<t.length; i++ ) {
81 compatibleTypes[i+1] = t[i];
84 this.displayedColumns = displayedColumns;
87 public Type[] getCompatibleTypes() {
88 return compatibleTypes;
91 public TypedKey<?>[] getDisplayedColumns() {
92 return displayedColumns;
97 public final static TypedKey<Manufacturer> MANUFACTURER = new TypedKey<Manufacturer>("Manufacturer", Manufacturer.class);
98 public final static TypedKey<String> PARTNO = new TypedKey<String>("PartNo",String.class);
99 public final static TypedKey<String> DESCRIPTION = new TypedKey<String>("Description", String.class);
100 public final static TypedKey<Type> TYPE = new TypedKey<Type>("Type",Type.class);
101 public final static TypedKey<Double> LENGTH = new TypedKey<Double>("Length", Double.class, UnitGroup.UNITS_LENGTH);
102 public final static TypedKey<Double> INNER_DIAMETER = new TypedKey<Double>("InnerDiameter", Double.class, UnitGroup.UNITS_LENGTH);
103 public final static TypedKey<Double> OUTER_DIAMETER = new TypedKey<Double>("OuterDiameter", Double.class, UnitGroup.UNITS_LENGTH);
104 public final static TypedKey<Double> SHOULDER_LENGTH = new TypedKey<Double>("ShoulderLength", Double.class, UnitGroup.UNITS_LENGTH);
105 public final static TypedKey<Double> SHOULDER_DIAMETER = new TypedKey<Double>("ShoulderDiameter", Double.class, UnitGroup.UNITS_LENGTH);
106 public final static TypedKey<Double> FORE_SHOULDER_LENGTH = new TypedKey<Double>("ForeShoulderLength",Double.class, UnitGroup.UNITS_LENGTH);
107 public final static TypedKey<Double> FORE_SHOULDER_DIAMETER = new TypedKey<Double>("ForeShoulderDiameter",Double.class, UnitGroup.UNITS_LENGTH);
108 public final static TypedKey<Double> FORE_OUTER_DIAMETER = new TypedKey<Double>("ForeOuterDiameter", Double.class, UnitGroup.UNITS_LENGTH);
109 public final static TypedKey<Shape> SHAPE = new TypedKey<Shape>("Shape", Shape.class);
110 public final static TypedKey<Material> MATERIAL = new TypedKey<Material>("Material", Material.class);
111 public final static TypedKey<Finish> FINISH = new TypedKey<Finish>("Finish", Finish.class);
112 public final static TypedKey<Double> THICKNESS = new TypedKey<Double>("Thickness", Double.class, UnitGroup.UNITS_LENGTH);
113 public final static TypedKey<Boolean> FILLED = new TypedKey<Boolean>("Filled", Boolean.class);
114 public final static TypedKey<Double> MASS = new TypedKey<Double>("Mass", Double.class, UnitGroup.UNITS_MASS);
116 public final static Map<String, TypedKey<?>> keyMap = new HashMap<String, TypedKey<?>>();
118 keyMap.put(MANUFACTURER.getName(), MANUFACTURER);
119 keyMap.put(PARTNO.getName(), PARTNO);
120 keyMap.put(TYPE.getName(), TYPE);
121 keyMap.put(DESCRIPTION.getName(), DESCRIPTION);
122 keyMap.put(LENGTH.getName(), LENGTH);
123 keyMap.put(INNER_DIAMETER.getName(), INNER_DIAMETER);
124 keyMap.put(OUTER_DIAMETER.getName(), OUTER_DIAMETER);
125 keyMap.put(SHOULDER_LENGTH.getName(), SHOULDER_LENGTH);
126 keyMap.put(SHOULDER_DIAMETER.getName(), SHOULDER_DIAMETER);
127 keyMap.put(FORE_SHOULDER_LENGTH.getName(), FORE_SHOULDER_LENGTH);
128 keyMap.put(FORE_SHOULDER_DIAMETER.getName(), FORE_SHOULDER_DIAMETER);
129 keyMap.put(FORE_OUTER_DIAMETER.getName(), FORE_OUTER_DIAMETER);
130 keyMap.put(SHAPE.getName(), SHAPE);
131 keyMap.put(MATERIAL.getName(), MATERIAL);
132 keyMap.put(FINISH.getName(), FINISH);
133 keyMap.put(THICKNESS.getName(), THICKNESS);
134 keyMap.put(FILLED.getName(), FILLED);
135 keyMap.put(MASS.getName(), MASS);
138 public static ComponentPreset create( TypedPropertyMap props ) throws InvalidComponentPresetException {
140 ComponentPreset preset = new ComponentPreset();
141 // First do validation.
142 if ( !props.containsKey(TYPE)) {
143 throw new InvalidComponentPresetException("No Type specified " + props.toString() );
146 if (!props.containsKey(MANUFACTURER)) {
147 throw new InvalidComponentPresetException("No Manufacturer specified " + props.toString() );
150 if (!props.containsKey(PARTNO)) {
151 throw new InvalidComponentPresetException("No PartNo specified " + props.toString() );
154 preset.properties.putAll(props);
156 // Should check for various bits of each of the types.
157 Type t = props.get(TYPE);
161 if ( !props.containsKey(LENGTH) ) {
162 throw new InvalidComponentPresetException( "No Length specified for body tube preset " + props.toString());
165 BodyTube bt = new BodyTube();
167 bt.setLength(props.get(LENGTH));
169 // Need to verify contains 2 of OD, thickness, ID. Compute the third.
170 boolean hasOd = props.containsKey(OUTER_DIAMETER);
171 boolean hasId = props.containsKey(INNER_DIAMETER);
172 boolean hasThickness = props.containsKey(THICKNESS);
175 double outerRadius = props.get(OUTER_DIAMETER)/2.0;
176 double thickness = 0;
177 bt.setOuterRadius( outerRadius );
179 thickness = outerRadius - props.get(INNER_DIAMETER)/2.0;
180 } else if ( hasThickness ) {
181 thickness = props.get(THICKNESS);
183 throw new InvalidComponentPresetException("Body tube preset underspecified " + props.toString());
185 bt.setThickness( thickness );
187 if ( ! hasId && ! hasThickness ) {
188 throw new InvalidComponentPresetException("Body tube preset underspecified " + props.toString());
190 double innerRadius = props.get(INNER_DIAMETER)/2.0;
191 double thickness = props.get(THICKNESS);
192 bt.setOuterRadius(innerRadius + thickness);
193 bt.setThickness(thickness);
196 preset.properties.put(OUTER_DIAMETER, bt.getOuterRadius() *2.0);
197 preset.properties.put(INNER_DIAMETER, bt.getInnerRadius() *2.0);
198 preset.properties.put(THICKNESS, bt.getThickness());
200 // Need to translate Mass to Density.
201 if ( props.containsKey(MASS) ) {
202 String materialName = "TubeCustom";
203 if ( props.containsKey(MATERIAL) ) {
204 materialName = props.get(MATERIAL).getName();
206 Material m = Material.newMaterial(Material.Type.BULK, materialName, props.get(MASS)/bt.getComponentVolume(), false);
207 preset.properties.put(MATERIAL, m);
214 if ( !props.containsKey(LENGTH) ) {
215 throw new InvalidComponentPresetException( "No Length specified for nose cone preset " + props.toString());
217 if ( !props.containsKey(SHAPE) ) {
218 throw new InvalidComponentPresetException( "No Shape specified for nose cone preset " + props.toString());
220 if ( !props.containsKey(OUTER_DIAMETER) ) {
221 throw new InvalidComponentPresetException( "No Outer Diameter specified for nose cone preset " + props.toString());
224 if ( props.containsKey(MASS) ) {
225 // compute a density for this component
226 double mass = props.get(MASS);
227 NoseCone nc = new NoseCone();
228 nc.loadPreset(preset);
229 double density = mass / nc.getComponentVolume();
231 String materialName = "NoseConeCustom";
232 if ( props.containsKey(MATERIAL) ) {
233 materialName = props.get(MATERIAL).getName();
236 Material m = Material.newMaterial(Material.Type.BULK, materialName,density, false);
237 preset.properties.put(MATERIAL, m);
244 if ( !props.containsKey(LENGTH) ) {
245 throw new InvalidComponentPresetException( "No Length specified for transition preset " + props.toString());
247 if ( !props.containsKey(OUTER_DIAMETER) ) {
248 throw new InvalidComponentPresetException( "No Outer Diameter specified for transition preset " + props.toString());
250 if ( !props.containsKey(FORE_OUTER_DIAMETER) ) {
251 throw new InvalidComponentPresetException( "No Fore Outer Diameter specified for transition preset " + props.toString());
254 if ( props.containsKey(MASS) ) {
255 // compute a density for this component
256 double mass = props.get(MASS);
257 Transition tr = new Transition();
258 tr.loadPreset(preset);
259 double density = mass / tr.getComponentVolume();
261 String materialName = "TransitionCustom";
262 if ( props.containsKey(MATERIAL) ) {
263 materialName = props.get(MATERIAL).getName();
266 Material m = Material.newMaterial(Material.Type.BULK, materialName,density, false);
267 preset.properties.put(MATERIAL, m);
274 preset.computeDigest();
280 // Private constructor to encourage use of factory.
281 private ComponentPreset() {
285 * Convenience method to retrieve the Type of this ComponentPreset.
289 public Type getType() {
290 return properties.get(TYPE);
294 * Convenience method to retrieve the Manufacturer of this ComponentPreset.
297 public Manufacturer getManufacturer() {
298 return properties.get(MANUFACTURER);
302 * Convenience method to retrieve the PartNo of this ComponentPreset.
305 public String getPartNo() {
306 return properties.get(PARTNO);
309 public String getDigest() {
313 public boolean has(Object key) {
314 return properties.containsKey(key);
317 public <T> T get(TypedKey<T> key) {
318 T value = properties.get(key);
320 throw new BugException("Preset did not contain key " + key + " " + properties.toString());
325 public boolean isFavorite() {
329 public void setFavorite(boolean favorite) {
330 this.favorite = favorite;
334 public int compareTo(ComponentPreset p2) {
335 int manuCompare = this.getManufacturer().getSimpleName().compareTo(p2.getManufacturer().getSimpleName());
336 if ( manuCompare != 0 )
339 int partNoCompare = this.getPartNo().compareTo(p2.getPartNo());
340 return partNoCompare;
344 public String toString() {
345 return get(MANUFACTURER).toString() + " " + get(PARTNO);
348 public String preferenceKey() {
349 return get(MANUFACTURER).toString() + "|" + get(PARTNO);
352 private void computeDigest() {
355 ByteArrayOutputStream bos = new ByteArrayOutputStream();
356 DataOutputStream os = new DataOutputStream(bos);
358 List<TypedKey<?>> keys = new ArrayList<TypedKey<?>>( properties.keySet());
360 Collections.sort(keys, new Comparator<TypedKey<?>>() {
362 public int compare( TypedKey<?> a, TypedKey<?> b ) {
363 return a.getName().compareTo(b.getName());
367 for ( TypedKey<?> key : keys ) {
369 Object value = properties.get(key);
371 os.writeBytes(key.getName());
373 if ( key.getType() == Double.class ) {
374 Double d = (Double) value;
376 } else if (key.getType() == String.class ) {
377 String s = (String) value;
379 } else if (key.getType() == Manufacturer.class ) {
380 String s = ((Manufacturer)value).getSimpleName();
382 } else if ( key.getType() == Finish.class ) {
383 String s = ((Finish)value).name();
385 } else if ( key.getType() == Type.class ) {
386 String s = ((Type)value).name();
388 } else if ( key.getType() == Boolean.class ) {
389 Boolean b = (Boolean) value;
391 } else if ( key.getType() == Material.class ) {
392 double d = ((Material)value).getDensity();
394 } else if ( key.getType() == Shape.class ) {
395 // FIXME - this is ugly to use the ordinal but what else?
396 int i = ((Shape)value).ordinal();
402 MessageDigest md5 = MessageDigest.getInstance("MD5");
403 digest = TextUtil.hexString(md5.digest( bos.toByteArray() ));
405 catch ( Exception e ) {
406 throw new BugException(e);