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.unit.UnitGroup;
18 import net.sf.openrocket.util.BugException;
19 import net.sf.openrocket.util.TextUtil;
23 * A model for a preset component.
25 * A preset component contains a component class type, manufacturer information,
26 * part information, and a method that returns a prototype of the preset component.
28 * @author Sampo Niskanen <sampo.niskanen@iki.fi>
30 // FIXME - Implement clone.
31 public class ComponentPreset implements Comparable<ComponentPreset> {
33 private final TypedPropertyMap properties = new TypedPropertyMap();
35 private boolean favorite = false;
36 private String digest = "";
39 BODY_TUBE( new TypedKey<?>[] {
40 ComponentPreset.MANUFACTURER,
41 ComponentPreset.PARTNO,
42 ComponentPreset.OUTER_DIAMETER,
43 ComponentPreset.INNER_DIAMETER,
44 ComponentPreset.LENGTH} ),
46 NOSE_CONE( new TypedKey<?>[] {
47 ComponentPreset.MANUFACTURER,
48 ComponentPreset.PARTNO,
49 ComponentPreset.OUTER_DIAMETER,
50 ComponentPreset.INNER_DIAMETER,
51 ComponentPreset.LENGTH} ) ;
53 Type[] compatibleTypes;
54 TypedKey<?>[] displayedColumns;
56 Type( TypedKey<?>[] displayedColumns) {
57 compatibleTypes = new Type[1];
58 compatibleTypes[0] = this;
59 this.displayedColumns = displayedColumns;
62 Type( Type[] t, TypedKey<?>[] displayedColumns ) {
64 compatibleTypes = new Type[t.length+1];
65 compatibleTypes[0] = this;
66 for( int i=0; i<t.length; i++ ) {
67 compatibleTypes[i+1] = t[i];
70 this.displayedColumns = displayedColumns;
73 public Type[] getCompatibleTypes() {
74 return compatibleTypes;
77 public TypedKey<?>[] getDisplayedColumns() {
78 return displayedColumns;
83 public final static TypedKey<Manufacturer> MANUFACTURER = new TypedKey<Manufacturer>("Manufacturer", Manufacturer.class);
84 public final static TypedKey<String> PARTNO = new TypedKey<String>("PartNo",String.class);
85 public final static TypedKey<Type> TYPE = new TypedKey<Type>("Type",Type.class);
86 public final static TypedKey<Double> LENGTH = new TypedKey<Double>("Length", Double.class, UnitGroup.UNITS_LENGTH);
87 public final static TypedKey<Double> INNER_DIAMETER = new TypedKey<Double>("InnerDiameter", Double.class, UnitGroup.UNITS_LENGTH);
88 public final static TypedKey<Double> OUTER_DIAMETER = new TypedKey<Double>("OuterDiameter", Double.class, UnitGroup.UNITS_LENGTH);
89 public final static TypedKey<Material> MATERIAL = new TypedKey<Material>("Material", Material.class);
90 public final static TypedKey<Finish> FINISH = new TypedKey<Finish>("Finish", Finish.class);
91 public final static TypedKey<Double> THICKNESS = new TypedKey<Double>("Thickness", Double.class, UnitGroup.UNITS_LENGTH);
92 public final static TypedKey<Boolean> FILLED = new TypedKey<Boolean>("Filled", Boolean.class);
93 public final static TypedKey<Double> MASS = new TypedKey<Double>("Mass", Double.class, UnitGroup.UNITS_MASS);
95 public final static Map<String, TypedKey<?>> keyMap = new HashMap<String, TypedKey<?>>();
97 keyMap.put(MANUFACTURER.getName(), MANUFACTURER);
98 keyMap.put(PARTNO.getName(), PARTNO);
99 keyMap.put(TYPE.getName(), TYPE);
100 keyMap.put(LENGTH.getName(), LENGTH);
101 keyMap.put(INNER_DIAMETER.getName(), INNER_DIAMETER);
102 keyMap.put(OUTER_DIAMETER.getName(), OUTER_DIAMETER);
103 keyMap.put(MATERIAL.getName(), MATERIAL);
104 keyMap.put(FINISH.getName(), FINISH);
105 keyMap.put(THICKNESS.getName(), THICKNESS);
106 keyMap.put(FILLED.getName(), FILLED);
107 keyMap.put(MASS.getName(), MASS);
110 public static ComponentPreset create( TypedPropertyMap props ) throws InvalidComponentPresetException {
112 ComponentPreset preset = new ComponentPreset();
113 // First do validation.
114 if ( !props.containsKey(TYPE)) {
115 throw new InvalidComponentPresetException("No Type specified " + props.toString() );
118 if (!props.containsKey(MANUFACTURER)) {
119 throw new InvalidComponentPresetException("No Manufacturer specified " + props.toString() );
122 if (!props.containsKey(PARTNO)) {
123 throw new InvalidComponentPresetException("No PartNo specified " + props.toString() );
126 preset.properties.putAll(props);
128 // Should check for various bits of each of the types.
129 Type t = props.get(TYPE);
133 if ( !props.containsKey(LENGTH) ) {
134 throw new InvalidComponentPresetException( "No Length specified for body tube preset " + props.toString());
137 BodyTube bt = new BodyTube();
139 bt.setLength(props.get(LENGTH));
141 // Need to verify contains 2 of OD, thickness, ID. Compute the third.
142 boolean hasOd = props.containsKey(OUTER_DIAMETER);
143 boolean hasId = props.containsKey(INNER_DIAMETER);
144 boolean hasThickness = props.containsKey(THICKNESS);
147 double outerRadius = props.get(OUTER_DIAMETER)/2.0;
148 double thickness = 0;
149 bt.setOuterRadius( outerRadius );
151 thickness = outerRadius - props.get(INNER_DIAMETER)/2.0;
152 } else if ( hasThickness ) {
153 thickness = props.get(THICKNESS);
155 throw new InvalidComponentPresetException("Body tube preset underspecified " + props.toString());
157 bt.setThickness( thickness );
159 if ( ! hasId && ! hasThickness ) {
160 throw new InvalidComponentPresetException("Body tube preset underspecified " + props.toString());
162 double innerRadius = props.get(INNER_DIAMETER)/2.0;
163 double thickness = props.get(THICKNESS);
164 bt.setOuterRadius(innerRadius + thickness);
165 bt.setThickness(thickness);
168 preset.properties.put(OUTER_DIAMETER, bt.getOuterRadius() *2.0);
169 preset.properties.put(INNER_DIAMETER, bt.getInnerRadius() *2.0);
170 preset.properties.put(THICKNESS, bt.getThickness());
172 // Need to translate Mass to Density.
173 if ( props.containsKey(MASS) ) {
174 String materialName = "TubeCustom";
175 if ( props.containsKey(MATERIAL) ) {
176 materialName = props.get(MATERIAL).getName();
178 Material m = Material.newMaterial(Material.Type.BULK, materialName, props.get(MASS)/bt.getComponentVolume(), false);
179 preset.properties.put(MATERIAL, m);
189 preset.computeDigest();
195 // Private constructor to encourage use of factory.
196 private ComponentPreset() {
200 * Convenience method to retrieve the Type of this ComponentPreset.
204 public Type getType() {
205 return properties.get(TYPE);
209 * Convenience method to retrieve the Manufacturer of this ComponentPreset.
212 public Manufacturer getManufacturer() {
213 return properties.get(MANUFACTURER);
217 * Convenience method to retrieve the PartNo of this ComponentPreset.
220 public String getPartNo() {
221 return properties.get(PARTNO);
224 public String getDigest() {
228 public boolean has(Object key) {
229 return properties.containsKey(key);
232 public <T> T get(TypedKey<T> key) {
233 T value = properties.get(key);
235 throw new BugException("Preset did not contain key " + key + " " + properties.toString());
240 public boolean isFavorite() {
244 public void setFavorite(boolean favorite) {
245 this.favorite = favorite;
249 public int compareTo(ComponentPreset p2) {
250 int manuCompare = this.getManufacturer().getSimpleName().compareTo(p2.getManufacturer().getSimpleName());
251 if ( manuCompare != 0 )
254 int partNoCompare = this.getPartNo().compareTo(p2.getPartNo());
255 return partNoCompare;
259 public String toString() {
260 return get(MANUFACTURER).toString() + " " + get(PARTNO);
263 public String preferenceKey() {
264 return get(MANUFACTURER).toString() + "|" + get(PARTNO);
267 private void computeDigest() {
270 ByteArrayOutputStream bos = new ByteArrayOutputStream();
271 DataOutputStream os = new DataOutputStream(bos);
273 List<TypedKey<?>> keys = new ArrayList<TypedKey<?>>( properties.keySet());
275 Collections.sort(keys, new Comparator<TypedKey<?>>() {
277 public int compare( TypedKey<?> a, TypedKey<?> b ) {
278 return a.getName().compareTo(b.getName());
282 for ( TypedKey<?> key : keys ) {
284 Object value = properties.get(key);
286 os.writeBytes(key.getName());
288 if ( key.getType() == Double.class ) {
289 Double d = (Double) value;
291 } else if (key.getType() == String.class ) {
292 String s = (String) value;
294 } else if (key.getType() == Manufacturer.class ) {
295 String s = ((Manufacturer)value).getSimpleName();
297 } else if ( key.getType() == Finish.class ) {
298 String s = ((Finish)value).name();
300 } else if ( key.getType() == Type.class ) {
301 String s = ((Type)value).name();
303 } else if ( key.getType() == Boolean.class ) {
304 Boolean b = (Boolean) value;
306 } else if ( key.getType() == Material.class ) {
307 double d = ((Material)value).getDensity();
313 MessageDigest md5 = MessageDigest.getInstance("MD5");
314 digest = TextUtil.hexString(md5.digest( bos.toByteArray() ));
316 catch ( Exception e ) {
317 throw new BugException(e);