package net.sf.openrocket.preset; import java.io.ByteArrayOutputStream; import java.io.DataOutputStream; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.Serializable; import java.lang.reflect.Field; 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; import java.util.Map.Entry; import net.sf.openrocket.material.Material; import net.sf.openrocket.motor.Manufacturer; 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; /** * A model for a preset component. * <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 implements Comparable<ComponentPreset>, Serializable { /** * */ private static final long serialVersionUID = 3199781221967306617L; private final TypedPropertyMap properties = new TypedPropertyMap(); private String digest = ""; public enum Type { 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, 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, 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, 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); public final static List<TypedKey<?>> ORDERED_KEY_LIST = Collections.unmodifiableList(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 )); // 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); } /** * 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) { throw new BugException("Preset did not contain key " + key + " " + properties.toString()); } return value; } @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(PARTNO); } public String preferenceKey() { return String.valueOf(get(MANUFACTURER)) + "|" + String.valueOf(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); } } private static class MaterialSerializationProxy implements Serializable { /** * */ private static final long serialVersionUID = 8704894438168047622L; String name; String type; boolean userDefined; Double density; } private void writeObject( ObjectOutputStream oos ) throws IOException { Map<String,Object> DTO = new HashMap<String,Object>(); for ( Entry<TypedKey<?>, Object> entry :properties.entrySet() ) { TypedKey<?> key = entry.getKey(); Object value = entry.getValue(); String keyName = key.getName(); if ( value instanceof Material ) { Material material = (Material) value; MaterialSerializationProxy m = new MaterialSerializationProxy(); m.name = material.getName(); m.type = material.getType().name(); m.density = material.getDensity(); m.userDefined = material.isUserDefined(); value = m; } DTO.put(keyName,value); } oos.writeObject(DTO); } @SuppressWarnings("unchecked") private void readObject( ObjectInputStream ois ) throws IOException, ClassNotFoundException, NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException { Map<String,Object> DTO = (Map<String,Object>) ois.readObject(); Field propField = ComponentPreset.class.getDeclaredField("properties"); propField.setAccessible(true); propField.set(this, new TypedPropertyMap()); for ( Entry<String,Object> entry : DTO.entrySet() ) { String keyName = entry.getKey(); Object value = entry.getValue(); if ( value instanceof MaterialSerializationProxy ) { MaterialSerializationProxy m = (MaterialSerializationProxy) value; value = Material.newMaterial(Material.Type.valueOf(m.type), m.name, m.density, m.userDefined); } if ( TYPE.getName().equals(keyName)) { this.properties.put(TYPE, (ComponentPreset.Type) value); } else { for( @SuppressWarnings("rawtypes") TypedKey k : ORDERED_KEY_LIST ) { if ( k.getName().equals(keyName)) { this.properties.put( k, value ); break; } } } } this.computeDigest(); } }