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