Fire the simulation changed message when resumed so any background simulation executi...
[debian/openrocket] / core / src / net / sf / openrocket / preset / ComponentPreset.java
index e3406577a6b1fd719c7f0eb8df84c2cb492c6525..12331495df47421e22aafa98ad6cca98ea1c76e3 100644 (file)
@@ -1,13 +1,23 @@
 package net.sf.openrocket.preset;
 
-import java.util.HashMap;
-import java.util.Map;
-
 import net.sf.openrocket.material.Material;
 import net.sf.openrocket.motor.Manufacturer;
-import net.sf.openrocket.rocketcomponent.BodyTube;
 import net.sf.openrocket.rocketcomponent.ExternalComponent.Finish;
+import net.sf.openrocket.rocketcomponent.Transition.Shape;
+import net.sf.openrocket.unit.UnitGroup;
 import net.sf.openrocket.util.BugException;
+import net.sf.openrocket.util.TextUtil;
+
+import java.io.ByteArrayOutputStream;
+import java.io.DataOutputStream;
+import java.security.MessageDigest;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
 
 
 /**
@@ -15,140 +25,246 @@ import net.sf.openrocket.util.BugException;
  * <p>
  * A preset component contains a component class type, manufacturer information,
  * part information, and a method that returns a prototype of the preset component.
- * 
+ *
  * @author Sampo Niskanen <sampo.niskanen@iki.fi>
  */
-public class ComponentPreset {
-       
+// FIXME - Implement clone.
+public class ComponentPreset implements Comparable<ComponentPreset> {
+
        private final TypedPropertyMap properties = new TypedPropertyMap();
-       
-       
-       // TODO - Implement clone.
-       
+
+       private boolean favorite = false;
+       private String digest = "";
+
        public enum Type {
-               BODY_TUBE,
-               NOSE_CONE
+               BODY_TUBE( new TypedKey<?>[] {
+                               ComponentPreset.MANUFACTURER,
+                               ComponentPreset.PARTNO,
+                               ComponentPreset.DESCRIPTION,
+                               ComponentPreset.INNER_DIAMETER,
+                               ComponentPreset.OUTER_DIAMETER,
+                               ComponentPreset.LENGTH} ),
+
+               NOSE_CONE( new TypedKey<?>[] {
+                               ComponentPreset.MANUFACTURER,
+                               ComponentPreset.PARTNO,
+                               ComponentPreset.DESCRIPTION,
+                               ComponentPreset.SHAPE,
+                               ComponentPreset.AFT_OUTER_DIAMETER,
+                               ComponentPreset.AFT_SHOULDER_DIAMETER,
+                ComponentPreset.AFT_SHOULDER_LENGTH,
+                               ComponentPreset.LENGTH} ),
+
+               TRANSITION( new TypedKey<?>[] {
+                               ComponentPreset.MANUFACTURER,
+                               ComponentPreset.PARTNO,
+                               ComponentPreset.DESCRIPTION,
+                               ComponentPreset.SHAPE,
+                               ComponentPreset.FORE_OUTER_DIAMETER,
+                               ComponentPreset.FORE_SHOULDER_DIAMETER,
+                               ComponentPreset.FORE_SHOULDER_LENGTH,
+                               ComponentPreset.AFT_OUTER_DIAMETER,
+                               ComponentPreset.AFT_SHOULDER_DIAMETER,
+                               ComponentPreset.AFT_SHOULDER_LENGTH,
+                               ComponentPreset.LENGTH} ),
+
+               TUBE_COUPLER( new TypedKey<?>[] {
+                               ComponentPreset.MANUFACTURER,
+                               ComponentPreset.PARTNO,
+                ComponentPreset.DESCRIPTION,
+                               ComponentPreset.OUTER_DIAMETER,
+                               ComponentPreset.INNER_DIAMETER,
+                               ComponentPreset.LENGTH} ),
+
+               BULK_HEAD( new TypedKey<?>[] {
+                               ComponentPreset.MANUFACTURER,
+                               ComponentPreset.PARTNO,
+                               ComponentPreset.DESCRIPTION,
+                               ComponentPreset.OUTER_DIAMETER,
+                               ComponentPreset.LENGTH} ),
+
+               CENTERING_RING( new TypedKey<?>[] {
+                               ComponentPreset.MANUFACTURER,
+                               ComponentPreset.PARTNO,
+                               ComponentPreset.DESCRIPTION,
+                               ComponentPreset.INNER_DIAMETER,
+                               ComponentPreset.OUTER_DIAMETER,
+                               ComponentPreset.LENGTH} ),
+
+               ENGINE_BLOCK( new TypedKey<?>[] {
+                               ComponentPreset.MANUFACTURER,
+                               ComponentPreset.PARTNO,
+                               ComponentPreset.DESCRIPTION,
+                               ComponentPreset.INNER_DIAMETER,
+                               ComponentPreset.OUTER_DIAMETER,
+                               ComponentPreset.LENGTH} ),
+
+               LAUNCH_LUG( new TypedKey<?>[] {
+                               ComponentPreset.MANUFACTURER,
+                               ComponentPreset.PARTNO,
+                               ComponentPreset.DESCRIPTION,
+                               ComponentPreset.INNER_DIAMETER,
+                               ComponentPreset.OUTER_DIAMETER,
+                               ComponentPreset.LENGTH} ),
+
+               STREAMER( new TypedKey<?>[] {
+                               ComponentPreset.MANUFACTURER,
+                               ComponentPreset.PARTNO,
+                ComponentPreset.DESCRIPTION,
+                               ComponentPreset.LENGTH,
+                               ComponentPreset.WIDTH,
+                               ComponentPreset.THICKNESS,
+                               ComponentPreset.MATERIAL} ),
+
+               PARACHUTE( new TypedKey<?>[] {
+                               ComponentPreset.MANUFACTURER,
+                               ComponentPreset.PARTNO,
+                ComponentPreset.DESCRIPTION,
+                               ComponentPreset.DIAMETER,
+                               ComponentPreset.SIDES,
+                               ComponentPreset.LINE_COUNT,
+                               ComponentPreset.LINE_LENGTH,
+                               ComponentPreset.LINE_MATERIAL,
+                               ComponentPreset.MATERIAL} );
+
+               TypedKey<?>[] displayedColumns;
+
+               Type( TypedKey<?>[] displayedColumns) {
+                       this.displayedColumns = displayedColumns;
+               }
+
+               public List<Type> getCompatibleTypes() {
+                       return compatibleTypeMap.get(Type.this);
+               }
+
+               public TypedKey<?>[] getDisplayedColumns() {
+                       return displayedColumns;
+               }
+
+               private static Map<Type,List<Type>> compatibleTypeMap = new HashMap<Type,List<Type>>();
+
+               static {
+                       compatibleTypeMap.put( BODY_TUBE, Arrays.asList( BODY_TUBE, TUBE_COUPLER, LAUNCH_LUG ) );
+                       compatibleTypeMap.put( TUBE_COUPLER, Arrays.asList( BODY_TUBE,TUBE_COUPLER, LAUNCH_LUG ) );
+                       compatibleTypeMap.put( LAUNCH_LUG, Arrays.asList( BODY_TUBE,TUBE_COUPLER, LAUNCH_LUG ) );
+                       compatibleTypeMap.put( CENTERING_RING, Arrays.asList( CENTERING_RING, ENGINE_BLOCK ) );
+                       compatibleTypeMap.put( NOSE_CONE, Arrays.asList( NOSE_CONE, TRANSITION));
+               }
+
        }
-       
+
        public final static TypedKey<Manufacturer> MANUFACTURER = new TypedKey<Manufacturer>("Manufacturer", Manufacturer.class);
        public final static TypedKey<String> PARTNO = new TypedKey<String>("PartNo",String.class);
+       public final static TypedKey<String> DESCRIPTION = new TypedKey<String>("Description", String.class);
        public final static TypedKey<Type> TYPE = new TypedKey<Type>("Type",Type.class);
-       public final static TypedKey<Double> LENGTH = new TypedKey<Double>("Length", Double.class);
-       public final static TypedKey<Double> INNER_DIAMETER = new TypedKey<Double>("InnerDiameter", Double.class);
-       public final static TypedKey<Double> OUTER_DIAMETER = new TypedKey<Double>("OuterDiameter", Double.class);
+       public final static TypedKey<Double> LENGTH = new TypedKey<Double>("Length", Double.class, UnitGroup.UNITS_LENGTH);
+       public final static TypedKey<Double> WIDTH = new TypedKey<Double>("Width", Double.class, UnitGroup.UNITS_LENGTH);
+       public final static TypedKey<Double> INNER_DIAMETER = new TypedKey<Double>("InnerDiameter", Double.class, UnitGroup.UNITS_LENGTH);
+       public final static TypedKey<Double> OUTER_DIAMETER = new TypedKey<Double>("OuterDiameter", Double.class, UnitGroup.UNITS_LENGTH);
+       public final static TypedKey<Double> FORE_SHOULDER_LENGTH = new TypedKey<Double>("ForeShoulderLength",Double.class, UnitGroup.UNITS_LENGTH);
+       public final static TypedKey<Double> FORE_SHOULDER_DIAMETER = new TypedKey<Double>("ForeShoulderDiameter",Double.class, UnitGroup.UNITS_LENGTH);
+       public final static TypedKey<Double> FORE_OUTER_DIAMETER = new TypedKey<Double>("ForeOuterDiameter", Double.class, UnitGroup.UNITS_LENGTH);
+       public final static TypedKey<Double> AFT_SHOULDER_LENGTH = new TypedKey<Double>("AftShoulderLength",Double.class, UnitGroup.UNITS_LENGTH);
+       public final static TypedKey<Double> AFT_SHOULDER_DIAMETER = new TypedKey<Double>("AftShoulderDiameter",Double.class, UnitGroup.UNITS_LENGTH);
+       public final static TypedKey<Double> AFT_OUTER_DIAMETER = new TypedKey<Double>("AftOuterDiameter", Double.class, UnitGroup.UNITS_LENGTH);
+       public final static TypedKey<Shape> SHAPE = new TypedKey<Shape>("Shape", Shape.class);
        public final static TypedKey<Material> MATERIAL = new TypedKey<Material>("Material", Material.class);
        public final static TypedKey<Finish> FINISH = new TypedKey<Finish>("Finish", Finish.class);
-       public final static TypedKey<Double> THICKNESS = new TypedKey<Double>("Thickness", Double.class);
+       public final static TypedKey<Double> THICKNESS = new TypedKey<Double>("Thickness", Double.class, UnitGroup.UNITS_LENGTH);
        public final static TypedKey<Boolean> FILLED = new TypedKey<Boolean>("Filled", Boolean.class);
-       public final static TypedKey<Double> MASS = new TypedKey<Double>("Mass", Double.class);
-       
-       public final static Map<String, TypedKey<?>> keyMap = new HashMap<String, TypedKey<?>>();
-       static {
-               keyMap.put(MANUFACTURER.getName(), MANUFACTURER);
-               keyMap.put(PARTNO.getName(), PARTNO);
-               keyMap.put(TYPE.getName(), TYPE);
-               keyMap.put(LENGTH.getName(), LENGTH);
-               keyMap.put(INNER_DIAMETER.getName(), INNER_DIAMETER);
-               keyMap.put(OUTER_DIAMETER.getName(), OUTER_DIAMETER);
-               keyMap.put(MATERIAL.getName(), MATERIAL);
-               keyMap.put(FINISH.getName(), FINISH);
-               keyMap.put(THICKNESS.getName(), THICKNESS);
-               keyMap.put(FILLED.getName(), FILLED);
-               keyMap.put(MASS.getName(), MASS);
-       }
-       
-       public static ComponentPreset create( TypedPropertyMap props ) throws InvalidComponentPresetException {
-               
-               ComponentPreset preset = new ComponentPreset();
-               // First do validation.
-               if ( !props.containsKey(TYPE)) {
-                       throw new InvalidComponentPresetException("No Type specified " + props.toString() );
-               }
-               
-               if (!props.containsKey(MANUFACTURER)) {
-                       throw new InvalidComponentPresetException("No Manufacturer specified " + props.toString() );
-               }
+       public final static TypedKey<Double> MASS = new TypedKey<Double>("Mass", Double.class, UnitGroup.UNITS_MASS);
+       public final static TypedKey<Double> DIAMETER = new TypedKey<Double>("Diameter", Double.class, UnitGroup.UNITS_LENGTH);
+       public final static TypedKey<Integer> SIDES = new TypedKey<Integer>("Sides", Integer.class);
+       public final static TypedKey<Integer> LINE_COUNT = new TypedKey<Integer>("LineCount", Integer.class);
+       public final static TypedKey<Double> LINE_LENGTH = new TypedKey<Double>("LineLength", Double.class, UnitGroup.UNITS_LENGTH);
+       public final static TypedKey<Material> LINE_MATERIAL = new TypedKey<Material>("LineMaterial", Material.class);
+    public final static TypedKey<byte[]> IMAGE = new TypedKey<byte[]>("Image", byte[].class);
 
-               if (!props.containsKey(PARTNO)) {
-                       throw new InvalidComponentPresetException("No PartNo specified " + props.toString() );
-               }
+       public final static List<TypedKey<?>> orderedKeyList = Arrays.<TypedKey<?>>asList(
+                       MANUFACTURER,
+                       PARTNO,
+                       DESCRIPTION,
+                       OUTER_DIAMETER,
+                       FORE_OUTER_DIAMETER,
+                       AFT_OUTER_DIAMETER,
+                       INNER_DIAMETER,
+                       LENGTH,
+                       WIDTH,
+                       AFT_SHOULDER_DIAMETER,
+                       AFT_SHOULDER_LENGTH,
+                       FORE_SHOULDER_DIAMETER,
+                       FORE_SHOULDER_LENGTH,
+                       SHAPE,
+                       THICKNESS,
+                       FILLED,
+                       DIAMETER,
+                       SIDES,
+                       LINE_COUNT,
+                       LINE_LENGTH,
+                       LINE_MATERIAL,
+                       MASS,
+                       FINISH,
+                       MATERIAL
+                       );
 
-               preset.properties.putAll(props);
-               
-               // Should check for various bits of each of the types.
-               Type t = props.get(TYPE);
-               switch ( t ) {
-               case BODY_TUBE: {
-                       
-                       if ( !props.containsKey(LENGTH) ) {
-                               throw new InvalidComponentPresetException( "No Length specified for body tube preset " + props.toString());
-                       }
-                       
-                       BodyTube bt = new BodyTube();
-                       
-                       bt.setLength(props.get(LENGTH));
-                       
-                       // Need to verify contains 2 of OD, thickness, ID.  Compute the third.
-                       boolean hasOd = props.containsKey(OUTER_DIAMETER);
-                       boolean hasId = props.containsKey(INNER_DIAMETER);
-                       boolean hasThickness = props.containsKey(THICKNESS);
-                       
-                       if ( hasOd ) {
-                               double outerRadius = props.get(OUTER_DIAMETER)/2.0;
-                               double thickness = 0;
-                               bt.setOuterRadius( outerRadius );
-                               if ( hasId ) {
-                                       thickness = outerRadius - props.get(INNER_DIAMETER)/2.0;
-                               } else if ( hasThickness ) {
-                                       thickness = props.get(THICKNESS);
-                               } else {
-                                       throw new InvalidComponentPresetException("Body tube preset underspecified " + props.toString());
-                               }
-                               bt.setThickness( thickness );
-                       } else {
-                               if ( ! hasId && ! hasThickness ) {
-                                       throw new InvalidComponentPresetException("Body tube preset underspecified " + props.toString());
-                               }
-                               double innerRadius = props.get(INNER_DIAMETER)/2.0;
-                               double thickness = props.get(THICKNESS);
-                               bt.setOuterRadius(innerRadius + thickness);
-                               bt.setThickness(thickness);
-                       }
 
-                       preset.properties.put(OUTER_DIAMETER, bt.getOuterRadius() *2.0);
-                       preset.properties.put(INNER_DIAMETER, bt.getInnerRadius() *2.0);
-                       preset.properties.put(THICKNESS, bt.getThickness());
-                       
-                       // Need to translate Mass to Density.
-                       if ( props.containsKey(MASS) ) {
-                               String materialName = "TubeCustom";
-                               if ( props.containsKey(MATERIAL) ) {
-                                       materialName = props.get(MATERIAL).getName();
-                               }
-                               Material m = Material.newMaterial(Material.Type.BULK, materialName, props.get(MASS)/bt.getComponentVolume(), false);
-                               preset.properties.put(MATERIAL, m);
-                       }
-                       
-                       break;
-               }
-               case NOSE_CONE: {
-                       break;
-               }
-               }
-               
-               return preset;
+       // package scope constructor to encourage use of factory.
+       ComponentPreset() {
+       }
 
+       /**
+        * Convenience method to retrieve the Type of this ComponentPreset.
+        *
+        * @return
+        */
+       public Type getType() {
+               return properties.get(TYPE);
        }
 
-       // Private constructor to encourage use of factory.
-       private ComponentPreset() {
-               
+       /**
+        * Convenience method to retrieve the Manufacturer of this ComponentPreset.
+        * @return
+        */
+       public Manufacturer getManufacturer() {
+               return properties.get(MANUFACTURER);
        }
-       
+
+       /**
+        * Convenience method to retrieve the PartNo of this ComponentPreset.
+        * @return
+        */
+       public String getPartNo() {
+               return properties.get(PARTNO);
+       }
+
+       public String getDigest() {
+               return digest;
+       }
+
        public boolean has(Object key) {
                return properties.containsKey(key);
        }
-       
+
+       /**
+        * Package scope so the ComponentPresetFactory can call it.
+        * @param other
+        */
+       void putAll(TypedPropertyMap other) {
+               if (other == null) {
+                       return;
+               }
+               properties.putAll(other);
+       }
+
+       /**
+        * Package scope so the ComponentPresetFactory can call it.
+        * @param key
+        * @param value
+        */
+       <T> void put( TypedKey<T> key, T value ) {
+               properties.put(key, value);
+       }
+
        public <T> T get(TypedKey<T> key) {
                T value = properties.get(key);
                if (value == null) {
@@ -156,11 +272,117 @@ public class ComponentPreset {
                }
                return (T) value;
        }
-       
+
+       public boolean isFavorite() {
+               return favorite;
+       }
+
+       public void setFavorite(boolean favorite) {
+               this.favorite = favorite;
+       }
+
+       @Override
+       public int compareTo(ComponentPreset p2) {
+               int manuCompare = this.getManufacturer().getSimpleName().compareTo(p2.getManufacturer().getSimpleName());
+               if ( manuCompare != 0 )
+                       return manuCompare;
+
+               int partNoCompare = this.getPartNo().compareTo(p2.getPartNo());
+               return partNoCompare;
+       }
+
        @Override
        public String toString() {
                return get(MANUFACTURER).toString() + " " + get(PARTNO);
        }
-       
-       
+
+       public String preferenceKey() {
+               return get(MANUFACTURER).toString() + "|" + get(PARTNO);
+       }
+
+    @Override
+    public boolean equals(final Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+
+        ComponentPreset that = (ComponentPreset) o;
+
+        if (digest != null ? !digest.equals(that.digest) : that.digest != null) {
+            return false;
+        }
+
+        return true;
+    }
+
+    @Override
+    public int hashCode() {
+        return digest != null ? digest.hashCode() : 0;
+    }
+
+    /**
+        * Package scope so the factory can call it.
+        */
+       void computeDigest() {
+
+               try {
+                       ByteArrayOutputStream bos = new ByteArrayOutputStream();
+                       DataOutputStream os = new DataOutputStream(bos);
+
+                       List<TypedKey<?>> keys = new ArrayList<TypedKey<?>>( properties.keySet());
+
+                       Collections.sort(keys, new Comparator<TypedKey<?>>() {
+                               @Override
+                               public int compare( TypedKey<?> a, TypedKey<?> b ) {
+                                       return a.getName().compareTo(b.getName());
+                               }
+                       });
+
+                       for ( TypedKey<?> key : keys  ) {
+
+                               Object value = properties.get(key);
+
+                               os.writeBytes(key.getName());
+
+                               if ( key.getType() == Double.class ) {
+                                       Double d = (Double) value;
+                                       os.writeDouble(d);
+                               } else if (key.getType() == String.class ) {
+                                       String s = (String) value;
+                                       os.writeBytes(s);
+                               } else if (key.getType() == Manufacturer.class ) {
+                                       String s = ((Manufacturer)value).getSimpleName();
+                                       os.writeBytes(s);
+                               } else if ( key.getType() == Finish.class ) {
+                                       String s = ((Finish)value).name();
+                                       os.writeBytes(s);
+                               } else if ( key.getType() == Type.class ) {
+                                       String s = ((Type)value).name();
+                                       os.writeBytes(s);
+                               } else if ( key.getType() == Boolean.class ) {
+                                       Boolean b = (Boolean) value;
+                                       os.writeBoolean(b);
+                               } else if ( key.getType() == Material.class ) {
+                                       double d = ((Material)value).getDensity();
+                                       os.writeDouble(d);
+                               } else if ( key.getType() == Shape.class ) {
+                                       // this is ugly to use the ordinal but what else?
+                                       int i = ((Shape)value).ordinal();
+                                       os.writeInt(i);
+                               }
+
+                       }
+
+                       MessageDigest md5 = MessageDigest.getInstance("MD5");
+                       digest = TextUtil.hexString(md5.digest( bos.toByteArray() ));
+               }
+               catch ( Exception e ) {
+            e.printStackTrace();
+                       throw new BugException(e);
+               }
+       }
+
 }