c1d562856e726d6cefa85423601fa41bba80cebb
[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.Shape;
19 import net.sf.openrocket.unit.UnitGroup;
20 import net.sf.openrocket.util.BugException;
21 import net.sf.openrocket.util.TextUtil;
22
23
24 /**
25  * A model for a preset component.
26  * <p>
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.
29  * 
30  * @author Sampo Niskanen <sampo.niskanen@iki.fi>
31  */
32 // FIXME - Implement clone.
33 public class ComponentPreset implements Comparable<ComponentPreset> {
34
35         private final TypedPropertyMap properties = new TypedPropertyMap();
36
37         private boolean favorite = false;
38         private String digest = "";
39
40         public enum Type {
41                 BODY_TUBE( new TypedKey<?>[] {
42                                 ComponentPreset.MANUFACTURER,
43                                 ComponentPreset.PARTNO,
44                                 ComponentPreset.OUTER_DIAMETER,
45                                 ComponentPreset.INNER_DIAMETER,
46                                 ComponentPreset.LENGTH} ),
47                                 
48                 NOSE_CONE( new TypedKey<?>[] {
49                                 ComponentPreset.MANUFACTURER,
50                                 ComponentPreset.PARTNO,
51                                 ComponentPreset.SHAPE,
52                                 ComponentPreset.OUTER_DIAMETER,
53                                 ComponentPreset.LENGTH} ) ;
54
55                 Type[] compatibleTypes;
56                 TypedKey<?>[] displayedColumns;
57
58                 Type( TypedKey<?>[] displayedColumns) {
59                         compatibleTypes = new Type[1];
60                         compatibleTypes[0] = this;
61                         this.displayedColumns = displayedColumns;
62                 }
63
64                 Type( Type[] t, TypedKey<?>[] displayedColumns ) {
65
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];
70                         }
71
72                         this.displayedColumns = displayedColumns;
73                 }
74
75                 public Type[] getCompatibleTypes() {
76                         return compatibleTypes;
77                 }
78
79                 public TypedKey<?>[] getDisplayedColumns() {
80                         return displayedColumns;
81                 }
82
83         }
84
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);
100
101         public final static Map<String, TypedKey<?>> keyMap = new HashMap<String, TypedKey<?>>();
102         static {
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);
118         }
119
120         public static ComponentPreset create( TypedPropertyMap props ) throws InvalidComponentPresetException {
121
122                 ComponentPreset preset = new ComponentPreset();
123                 // First do validation.
124                 if ( !props.containsKey(TYPE)) {
125                         throw new InvalidComponentPresetException("No Type specified " + props.toString() );
126                 }
127
128                 if (!props.containsKey(MANUFACTURER)) {
129                         throw new InvalidComponentPresetException("No Manufacturer specified " + props.toString() );
130                 }
131
132                 if (!props.containsKey(PARTNO)) {
133                         throw new InvalidComponentPresetException("No PartNo specified " + props.toString() );
134                 }
135
136                 preset.properties.putAll(props);
137
138                 // Should check for various bits of each of the types.
139                 Type t = props.get(TYPE);
140                 switch ( t ) {
141                 case BODY_TUBE: {
142
143                         if ( !props.containsKey(LENGTH) ) {
144                                 throw new InvalidComponentPresetException( "No Length specified for body tube preset " + props.toString());
145                         }
146
147                         BodyTube bt = new BodyTube();
148
149                         bt.setLength(props.get(LENGTH));
150
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);
155
156                         if ( hasOd ) {
157                                 double outerRadius = props.get(OUTER_DIAMETER)/2.0;
158                                 double thickness = 0;
159                                 bt.setOuterRadius( outerRadius );
160                                 if ( hasId ) {
161                                         thickness = outerRadius - props.get(INNER_DIAMETER)/2.0;
162                                 } else if ( hasThickness ) {
163                                         thickness = props.get(THICKNESS);
164                                 } else {
165                                         throw new InvalidComponentPresetException("Body tube preset underspecified " + props.toString());
166                                 }
167                                 bt.setThickness( thickness );
168                         } else {
169                                 if ( ! hasId && ! hasThickness ) {
170                                         throw new InvalidComponentPresetException("Body tube preset underspecified " + props.toString());
171                                 }
172                                 double innerRadius = props.get(INNER_DIAMETER)/2.0;
173                                 double thickness = props.get(THICKNESS);
174                                 bt.setOuterRadius(innerRadius + thickness);
175                                 bt.setThickness(thickness);
176                         }
177
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());
181
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();
187                                 }
188                                 Material m = Material.newMaterial(Material.Type.BULK, materialName, props.get(MASS)/bt.getComponentVolume(), false);
189                                 preset.properties.put(MATERIAL, m);
190                         }
191
192                         break;
193                 }
194                 case NOSE_CONE: {
195                         if ( !props.containsKey(LENGTH) ) {
196                                 throw new InvalidComponentPresetException( "No Length specified for nose cone preset " + props.toString());
197                         }
198                         if ( !props.containsKey(SHAPE) ) {
199                                 throw new InvalidComponentPresetException( "No Shape specified for nose cone preset " + props.toString());
200                         }
201                         if ( !props.containsKey(OUTER_DIAMETER) ) {
202                                 throw new InvalidComponentPresetException( "No Outer Diameter specified for nose cone preset " + props.toString());
203                         }
204                         
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();
211
212                                 String materialName = "NoseConeCustom";
213                                 if ( props.containsKey(MATERIAL) ) {
214                                         materialName = props.get(MATERIAL).getName();
215                                 }
216                                 
217                                 Material m = Material.newMaterial(Material.Type.BULK, materialName,density, false);
218                                 preset.properties.put(MATERIAL, m);
219
220                         }
221                         break;
222                 }
223                 }
224
225                 preset.computeDigest();
226
227                 return preset;
228
229         }
230
231         // Private constructor to encourage use of factory.
232         private ComponentPreset() {
233         }
234
235         /**
236          * Convenience method to retrieve the Type of this ComponentPreset.
237          * 
238          * @return
239          */
240         public Type getType() {
241                 return properties.get(TYPE);
242         }
243
244         /**
245          * Convenience method to retrieve the Manufacturer of this ComponentPreset.
246          * @return
247          */
248         public Manufacturer getManufacturer() {
249                 return properties.get(MANUFACTURER);
250         }
251
252         /**
253          * Convenience method to retrieve the PartNo of this ComponentPreset.
254          * @return
255          */
256         public String getPartNo() {
257                 return properties.get(PARTNO);
258         }
259
260         public String getDigest() {
261                 return digest;
262         }
263
264         public boolean has(Object key) {
265                 return properties.containsKey(key);
266         }
267
268         public <T> T get(TypedKey<T> key) {
269                 T value = properties.get(key);
270                 if (value == null) {
271                         throw new BugException("Preset did not contain key " + key + " " + properties.toString());
272                 }
273                 return (T) value;
274         }
275
276         public boolean isFavorite() {
277                 return favorite;
278         }
279
280         public void setFavorite(boolean favorite) {
281                 this.favorite = favorite;
282         }
283
284         @Override
285         public int compareTo(ComponentPreset p2) {
286                 int manuCompare = this.getManufacturer().getSimpleName().compareTo(p2.getManufacturer().getSimpleName());
287                 if ( manuCompare != 0 )
288                         return manuCompare;
289
290                 int partNoCompare = this.getPartNo().compareTo(p2.getPartNo());
291                 return partNoCompare;
292         }
293
294         @Override
295         public String toString() {
296                 return get(MANUFACTURER).toString() + " " + get(PARTNO);
297         }
298
299         public String preferenceKey() {
300                 return get(MANUFACTURER).toString() + "|" + get(PARTNO);
301         }
302
303         private void computeDigest() {
304
305                 try {
306                         ByteArrayOutputStream bos = new ByteArrayOutputStream();
307                         DataOutputStream os = new DataOutputStream(bos);
308
309                         List<TypedKey<?>> keys = new ArrayList<TypedKey<?>>( properties.keySet());
310
311                         Collections.sort(keys, new Comparator<TypedKey<?>>() {
312                                 @Override
313                                 public int compare( TypedKey<?> a, TypedKey<?> b ) {
314                                         return a.getName().compareTo(b.getName());
315                                 }
316                         });
317
318                         for ( TypedKey<?> key : keys  ) {
319
320                                 Object value = properties.get(key);
321
322                                 os.writeBytes(key.getName());
323
324                                 if ( key.getType() == Double.class ) {
325                                         Double d = (Double) value;
326                                         os.writeDouble(d);
327                                 } else if (key.getType() == String.class ) {
328                                         String s = (String) value;
329                                         os.writeBytes(s);
330                                 } else if (key.getType() == Manufacturer.class ) {
331                                         String s = ((Manufacturer)value).getSimpleName();
332                                         os.writeBytes(s);
333                                 } else if ( key.getType() == Finish.class ) {
334                                         String s = ((Finish)value).name();
335                                         os.writeBytes(s);
336                                 } else if ( key.getType() == Type.class ) {
337                                         String s = ((Type)value).name();
338                                         os.writeBytes(s);
339                                 } else if ( key.getType() == Boolean.class ) {
340                                         Boolean b = (Boolean) value;
341                                         os.writeBoolean(b);
342                                 } else if ( key.getType() == Material.class ) {
343                                         double d = ((Material)value).getDensity();
344                                         os.writeDouble(d);
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();
348                                         os.writeInt(i);
349                                 }
350
351                         }
352
353                         MessageDigest md5 = MessageDigest.getInstance("MD5");
354                         digest = TextUtil.hexString(md5.digest( bos.toByteArray() ));
355                 }
356                 catch ( Exception e ) {
357                         throw new BugException(e);
358                 }
359         }
360
361 }