Implement Transition preset components.
[debian/openrocket] / core / src / net / sf / openrocket / preset / ComponentPreset.java
1 package net.sf.openrocket.preset;
2
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;
11 import java.util.Map;
12
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;
23
24
25 /**
26  * A model for a preset component.
27  * <p>
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.
30  * 
31  * @author Sampo Niskanen <sampo.niskanen@iki.fi>
32  */
33 // FIXME - Implement clone.
34 public class ComponentPreset implements Comparable<ComponentPreset> {
35
36         private final TypedPropertyMap properties = new TypedPropertyMap();
37
38         private boolean favorite = false;
39         private String digest = "";
40
41         public enum Type {
42                 BODY_TUBE( new TypedKey<?>[] {
43                                 ComponentPreset.MANUFACTURER,
44                                 ComponentPreset.PARTNO,
45                                 ComponentPreset.OUTER_DIAMETER,
46                                 ComponentPreset.INNER_DIAMETER,
47                                 ComponentPreset.LENGTH} ),
48                                 
49                 NOSE_CONE( new TypedKey<?>[] {
50                                 ComponentPreset.MANUFACTURER,
51                                 ComponentPreset.PARTNO,
52                                 ComponentPreset.DESCRIPTION,
53                                 ComponentPreset.SHAPE,
54                                 ComponentPreset.OUTER_DIAMETER,
55                                 ComponentPreset.LENGTH} ),
56
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
65                                 } ) ;
66
67                 Type[] compatibleTypes;
68                 TypedKey<?>[] displayedColumns;
69
70                 Type( TypedKey<?>[] displayedColumns) {
71                         compatibleTypes = new Type[1];
72                         compatibleTypes[0] = this;
73                         this.displayedColumns = displayedColumns;
74                 }
75
76                 Type( Type[] t, TypedKey<?>[] displayedColumns ) {
77
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];
82                         }
83
84                         this.displayedColumns = displayedColumns;
85                 }
86
87                 public Type[] getCompatibleTypes() {
88                         return compatibleTypes;
89                 }
90
91                 public TypedKey<?>[] getDisplayedColumns() {
92                         return displayedColumns;
93                 }
94
95         }
96
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);
115
116         public final static Map<String, TypedKey<?>> keyMap = new HashMap<String, TypedKey<?>>();
117         static {
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);
136         }
137
138         public static ComponentPreset create( TypedPropertyMap props ) throws InvalidComponentPresetException {
139
140                 ComponentPreset preset = new ComponentPreset();
141                 // First do validation.
142                 if ( !props.containsKey(TYPE)) {
143                         throw new InvalidComponentPresetException("No Type specified " + props.toString() );
144                 }
145
146                 if (!props.containsKey(MANUFACTURER)) {
147                         throw new InvalidComponentPresetException("No Manufacturer specified " + props.toString() );
148                 }
149
150                 if (!props.containsKey(PARTNO)) {
151                         throw new InvalidComponentPresetException("No PartNo specified " + props.toString() );
152                 }
153
154                 preset.properties.putAll(props);
155
156                 // Should check for various bits of each of the types.
157                 Type t = props.get(TYPE);
158                 switch ( t ) {
159                 case BODY_TUBE: {
160
161                         if ( !props.containsKey(LENGTH) ) {
162                                 throw new InvalidComponentPresetException( "No Length specified for body tube preset " + props.toString());
163                         }
164
165                         BodyTube bt = new BodyTube();
166
167                         bt.setLength(props.get(LENGTH));
168
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);
173
174                         if ( hasOd ) {
175                                 double outerRadius = props.get(OUTER_DIAMETER)/2.0;
176                                 double thickness = 0;
177                                 bt.setOuterRadius( outerRadius );
178                                 if ( hasId ) {
179                                         thickness = outerRadius - props.get(INNER_DIAMETER)/2.0;
180                                 } else if ( hasThickness ) {
181                                         thickness = props.get(THICKNESS);
182                                 } else {
183                                         throw new InvalidComponentPresetException("Body tube preset underspecified " + props.toString());
184                                 }
185                                 bt.setThickness( thickness );
186                         } else {
187                                 if ( ! hasId && ! hasThickness ) {
188                                         throw new InvalidComponentPresetException("Body tube preset underspecified " + props.toString());
189                                 }
190                                 double innerRadius = props.get(INNER_DIAMETER)/2.0;
191                                 double thickness = props.get(THICKNESS);
192                                 bt.setOuterRadius(innerRadius + thickness);
193                                 bt.setThickness(thickness);
194                         }
195
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());
199
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();
205                                 }
206                                 Material m = Material.newMaterial(Material.Type.BULK, materialName, props.get(MASS)/bt.getComponentVolume(), false);
207                                 preset.properties.put(MATERIAL, m);
208                         }
209
210                         break;
211                 }
212                 case NOSE_CONE: {
213                         
214                         if ( !props.containsKey(LENGTH) ) {
215                                 throw new InvalidComponentPresetException( "No Length specified for nose cone preset " + props.toString());
216                         }
217                         if ( !props.containsKey(SHAPE) ) {
218                                 throw new InvalidComponentPresetException( "No Shape specified for nose cone preset " + props.toString());
219                         }
220                         if ( !props.containsKey(OUTER_DIAMETER) ) {
221                                 throw new InvalidComponentPresetException( "No Outer Diameter specified for nose cone preset " + props.toString());
222                         }
223                         
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();
230
231                                 String materialName = "NoseConeCustom";
232                                 if ( props.containsKey(MATERIAL) ) {
233                                         materialName = props.get(MATERIAL).getName();
234                                 }
235                                 
236                                 Material m = Material.newMaterial(Material.Type.BULK, materialName,density, false);
237                                 preset.properties.put(MATERIAL, m);
238
239                         }
240                         break;
241                 }
242                 case TRANSITION: {
243                         
244                         if ( !props.containsKey(LENGTH) ) {
245                                 throw new InvalidComponentPresetException( "No Length specified for transition preset " + props.toString());
246                         }
247                         if ( !props.containsKey(OUTER_DIAMETER) ) {
248                                 throw new InvalidComponentPresetException( "No Outer Diameter specified for transition preset " + props.toString());
249                         }
250                         if ( !props.containsKey(FORE_OUTER_DIAMETER) ) {
251                                 throw new InvalidComponentPresetException( "No Fore Outer Diameter specified for transition preset " + props.toString());
252                         }
253                         
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();
260
261                                 String materialName = "TransitionCustom";
262                                 if ( props.containsKey(MATERIAL) ) {
263                                         materialName = props.get(MATERIAL).getName();
264                                 }
265                                 
266                                 Material m = Material.newMaterial(Material.Type.BULK, materialName,density, false);
267                                 preset.properties.put(MATERIAL, m);
268
269                         }
270                         break;
271                 }
272                 }
273
274                 preset.computeDigest();
275
276                 return preset;
277
278         }
279
280         // Private constructor to encourage use of factory.
281         private ComponentPreset() {
282         }
283
284         /**
285          * Convenience method to retrieve the Type of this ComponentPreset.
286          * 
287          * @return
288          */
289         public Type getType() {
290                 return properties.get(TYPE);
291         }
292
293         /**
294          * Convenience method to retrieve the Manufacturer of this ComponentPreset.
295          * @return
296          */
297         public Manufacturer getManufacturer() {
298                 return properties.get(MANUFACTURER);
299         }
300
301         /**
302          * Convenience method to retrieve the PartNo of this ComponentPreset.
303          * @return
304          */
305         public String getPartNo() {
306                 return properties.get(PARTNO);
307         }
308
309         public String getDigest() {
310                 return digest;
311         }
312
313         public boolean has(Object key) {
314                 return properties.containsKey(key);
315         }
316
317         public <T> T get(TypedKey<T> key) {
318                 T value = properties.get(key);
319                 if (value == null) {
320                         throw new BugException("Preset did not contain key " + key + " " + properties.toString());
321                 }
322                 return (T) value;
323         }
324
325         public boolean isFavorite() {
326                 return favorite;
327         }
328
329         public void setFavorite(boolean favorite) {
330                 this.favorite = favorite;
331         }
332
333         @Override
334         public int compareTo(ComponentPreset p2) {
335                 int manuCompare = this.getManufacturer().getSimpleName().compareTo(p2.getManufacturer().getSimpleName());
336                 if ( manuCompare != 0 )
337                         return manuCompare;
338
339                 int partNoCompare = this.getPartNo().compareTo(p2.getPartNo());
340                 return partNoCompare;
341         }
342
343         @Override
344         public String toString() {
345                 return get(MANUFACTURER).toString() + " " + get(PARTNO);
346         }
347
348         public String preferenceKey() {
349                 return get(MANUFACTURER).toString() + "|" + get(PARTNO);
350         }
351
352         private void computeDigest() {
353
354                 try {
355                         ByteArrayOutputStream bos = new ByteArrayOutputStream();
356                         DataOutputStream os = new DataOutputStream(bos);
357
358                         List<TypedKey<?>> keys = new ArrayList<TypedKey<?>>( properties.keySet());
359
360                         Collections.sort(keys, new Comparator<TypedKey<?>>() {
361                                 @Override
362                                 public int compare( TypedKey<?> a, TypedKey<?> b ) {
363                                         return a.getName().compareTo(b.getName());
364                                 }
365                         });
366
367                         for ( TypedKey<?> key : keys  ) {
368
369                                 Object value = properties.get(key);
370
371                                 os.writeBytes(key.getName());
372
373                                 if ( key.getType() == Double.class ) {
374                                         Double d = (Double) value;
375                                         os.writeDouble(d);
376                                 } else if (key.getType() == String.class ) {
377                                         String s = (String) value;
378                                         os.writeBytes(s);
379                                 } else if (key.getType() == Manufacturer.class ) {
380                                         String s = ((Manufacturer)value).getSimpleName();
381                                         os.writeBytes(s);
382                                 } else if ( key.getType() == Finish.class ) {
383                                         String s = ((Finish)value).name();
384                                         os.writeBytes(s);
385                                 } else if ( key.getType() == Type.class ) {
386                                         String s = ((Type)value).name();
387                                         os.writeBytes(s);
388                                 } else if ( key.getType() == Boolean.class ) {
389                                         Boolean b = (Boolean) value;
390                                         os.writeBoolean(b);
391                                 } else if ( key.getType() == Material.class ) {
392                                         double d = ((Material)value).getDensity();
393                                         os.writeDouble(d);
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();
397                                         os.writeInt(i);
398                                 }
399
400                         }
401
402                         MessageDigest md5 = MessageDigest.getInstance("MD5");
403                         digest = TextUtil.hexString(md5.digest( bos.toByteArray() ));
404                 }
405                 catch ( Exception e ) {
406                         throw new BugException(e);
407                 }
408         }
409
410 }