package net.sf.openrocket.database.motor; import java.text.Collator; import java.util.Collections; import java.util.Comparator; import java.util.IdentityHashMap; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; import net.sf.openrocket.motor.DesignationComparator; import net.sf.openrocket.motor.Manufacturer; import net.sf.openrocket.motor.Motor; import net.sf.openrocket.motor.Motor.Type; import net.sf.openrocket.motor.ThrustCurveMotor; import net.sf.openrocket.util.ArrayList; import net.sf.openrocket.util.MathUtil; public class ThrustCurveMotorSet implements Comparable<ThrustCurveMotorSet> { // Comparators: private static final Collator COLLATOR = Collator.getInstance(Locale.US); static { COLLATOR.setStrength(Collator.PRIMARY); } private static final DesignationComparator DESIGNATION_COMPARATOR = new DesignationComparator(); private static final ThrustCurveMotorComparator comparator = new ThrustCurveMotorComparator(); private final ArrayList<ThrustCurveMotor> motors = new ArrayList<ThrustCurveMotor>(); private final Map<ThrustCurveMotor, String> digestMap = new IdentityHashMap<ThrustCurveMotor, String>(); private final List<Double> delays = new ArrayList<Double>(); private Manufacturer manufacturer = null; private String designation = null; private String simplifiedDesignation = null; private double diameter = -1; private double length = -1; private long totalImpulse = 0; private Motor.Type type = Motor.Type.UNKNOWN; public void addMotor(ThrustCurveMotor motor) { // Check for first insertion if (motors.isEmpty()) { manufacturer = motor.getManufacturer(); designation = motor.getDesignation(); simplifiedDesignation = simplifyDesignation(designation); diameter = motor.getDiameter(); length = motor.getLength(); totalImpulse = Math.round((motor.getTotalImpulseEstimate())); } // Verify that the motor can be added if (!matches(motor)) { throw new IllegalArgumentException("Motor does not match the set:" + " manufacturer=" + manufacturer + " designation=" + designation + " diameter=" + diameter + " length=" + length + " set_size=" + motors.size() + " motor=" + motor); } // Update the type if now known if (type == Motor.Type.UNKNOWN) { type = motor.getMotorType(); // Add "Plugged" option if hybrid if (type == Motor.Type.HYBRID) { if (!delays.contains(Motor.PLUGGED)) { delays.add(Motor.PLUGGED); } } } // Change the simplified designation if necessary if (!designation.equalsIgnoreCase(motor.getDesignation().trim())) { designation = simplifiedDesignation; } // Add the standard delays for (double d : motor.getStandardDelays()) { d = Math.rint(d); if (!delays.contains(d)) { delays.add(d); } } Collections.sort(delays); // Check whether to add as new motor or overwrite existing final String digest = motor.getDigest(); for (int index = 0; index < motors.size(); index++) { Motor m = motors.get(index); if (digest.equals(digestMap.get(m)) && motor.getDesignation().equals(m.getDesignation())) { // Match found, check which one to keep (or both) based on comment String newCmt = motor.getDescription().replaceAll("\\s+", " ").trim(); String oldCmt = m.getDescription().replaceAll("\\s+", " ").trim(); if (newCmt.length() == 0 || newCmt.equals(oldCmt)) { // Do not replace and do not add return; } else if (oldCmt.length() == 0) { // Replace existing motor motors.set(index, motor); digestMap.put(motor, digest); return; } // else continue search and add both } } // Motor not present, add it motors.add(motor); digestMap.put(motor, digest); Collections.sort(motors, comparator); } public boolean matches(ThrustCurveMotor m) { if (motors.isEmpty()) return true; if (manufacturer != m.getManufacturer()) return false; if (!MathUtil.equals(diameter, m.getDiameter())) return false; if (!MathUtil.equals(length, m.getLength())) return false; if ((type != Type.UNKNOWN) && (m.getMotorType() != Type.UNKNOWN) && (type != m.getMotorType())) { return false; } if (!simplifiedDesignation.equalsIgnoreCase(simplifyDesignation(m.getDesignation()))) return false; return true; } public List<ThrustCurveMotor> getMotors() { return motors.clone(); } public int getMotorCount() { return motors.size(); } /** * Return the standard delays applicable to this motor type. This is a union of * all the delays of the motors included in this set. * @return the delays */ public List<Double> getDelays() { return Collections.unmodifiableList(delays); } /** * Return the manufacturer of this motor type. * @return the manufacturer */ public Manufacturer getManufacturer() { return manufacturer; } /** * Return the designation of this motor type. This is either the exact or simplified * designation, depending on what motors have been added. * @return the designation */ public String getDesignation() { return designation; } /** * Return the diameter of this motor type. * @return the diameter */ public double getDiameter() { return diameter; } /** * Return the length of this motor type. * @return the length */ public double getLength() { return length; } /** * Return the type of this motor type. If any of the added motors has a type different * from UNKNOWN, then that type will be returned. * @return the type */ public Motor.Type getType() { return type; } /** * Return the estimated total impulse for this motor type. * @return estimated total impulse */ public long getTotalImpuse() { return totalImpulse; } @Override public String toString() { return "ThrustCurveMotorSet[" + manufacturer + " " + designation + ", type=" + type + ", count=" + motors.size() + "]"; } private static final Pattern SIMPLIFY_PATTERN = Pattern.compile("^[0-9]*[ -]*([A-Z][0-9]+).*"); /** * Simplify a motor designation, if possible. This attempts to reduce the designation * into a simple letter + number notation for the impulse class and average thrust. * * @param str the designation to simplify * @return the simplified designation, or the string itself if the format was not detected */ public static String simplifyDesignation(String str) { str = str.trim(); Matcher m = SIMPLIFY_PATTERN.matcher(str); if (m.matches()) { return m.group(1); } else { return str.replaceAll("\\s", ""); } } /** * Comparator for deciding in which order to display matching motors. */ private static class ThrustCurveMotorComparator implements Comparator<ThrustCurveMotor> { @Override public int compare(ThrustCurveMotor o1, ThrustCurveMotor o2) { // 1. Designation if (!o1.getDesignation().equals(o2.getDesignation())) { return o1.getDesignation().compareTo(o2.getDesignation()); } // 2. Number of data points (more is better) if (o1.getTimePoints().length != o2.getTimePoints().length) { return o2.getTimePoints().length - o1.getTimePoints().length; } // 3. Comment length (longer is better) return o2.getDescription().length() - o1.getDescription().length(); } } @Override public int compareTo(ThrustCurveMotorSet other) { int value; // 1. Manufacturer value = COLLATOR.compare(this.manufacturer.getDisplayName(), other.manufacturer.getDisplayName()); if (value != 0) return value; // 2. Designation value = DESIGNATION_COMPARATOR.compare(this.designation, other.designation); if (value != 0) return value; // 3. Diameter value = (int) ((this.diameter - other.diameter) * 1000000); if (value != 0) return value; // 4. Length value = (int) ((this.length - other.length) * 1000000); return value; } }