create changelog entry
[debian/openrocket] / core / src / net / sf / openrocket / database / ThrustCurveMotorSet.java
1 package net.sf.openrocket.database;
2
3 import java.text.Collator;
4 import java.util.Collections;
5 import java.util.Comparator;
6 import java.util.IdentityHashMap;
7 import java.util.List;
8 import java.util.Locale;
9 import java.util.Map;
10 import java.util.regex.Matcher;
11 import java.util.regex.Pattern;
12
13 import net.sf.openrocket.motor.DesignationComparator;
14 import net.sf.openrocket.motor.Manufacturer;
15 import net.sf.openrocket.motor.Motor;
16 import net.sf.openrocket.motor.Motor.Type;
17 import net.sf.openrocket.motor.ThrustCurveMotor;
18 import net.sf.openrocket.util.ArrayList;
19 import net.sf.openrocket.util.MathUtil;
20
21 public class ThrustCurveMotorSet implements Comparable<ThrustCurveMotorSet> {
22         
23         //  Comparators:
24         private static final Collator COLLATOR = Collator.getInstance(Locale.US);
25         static {
26                 COLLATOR.setStrength(Collator.PRIMARY);
27         }
28         private static final DesignationComparator DESIGNATION_COMPARATOR = new DesignationComparator();
29         private static final ThrustCurveMotorComparator comparator = new ThrustCurveMotorComparator();
30         
31
32
33         private final ArrayList<ThrustCurveMotor> motors = new ArrayList<ThrustCurveMotor>();
34         private final Map<ThrustCurveMotor, String> digestMap =
35                         new IdentityHashMap<ThrustCurveMotor, String>();
36         
37         private final List<Double> delays = new ArrayList<Double>();
38         
39         private Manufacturer manufacturer = null;
40         private String designation = null;
41         private String simplifiedDesignation = null;
42         private double diameter = -1;
43         private double length = -1;
44         private Motor.Type type = Motor.Type.UNKNOWN;
45         
46         
47
48         public void addMotor(ThrustCurveMotor motor) {
49                 
50                 // Check for first insertion
51                 if (motors.isEmpty()) {
52                         manufacturer = motor.getManufacturer();
53                         designation = motor.getDesignation();
54                         simplifiedDesignation = simplifyDesignation(designation);
55                         diameter = motor.getDiameter();
56                         length = motor.getLength();
57                 }
58                 
59                 // Verify that the motor can be added
60                 if (!matches(motor)) {
61                         throw new IllegalArgumentException("Motor does not match the set:" +
62                                         " manufacturer=" + manufacturer +
63                                         " designation=" + designation +
64                                         " diameter=" + diameter +
65                                         " length=" + length +
66                                         " set_size=" + motors.size() +
67                                         " motor=" + motor);
68                 }
69                 
70                 // Update the type if now known
71                 if (type == Motor.Type.UNKNOWN) {
72                         type = motor.getMotorType();
73                         // Add "Plugged" option if hybrid
74                         if (type == Motor.Type.HYBRID) {
75                                 if (!delays.contains(Motor.PLUGGED)) {
76                                         delays.add(Motor.PLUGGED);
77                                 }
78                         }
79                 }
80                 
81                 // Change the simplified designation if necessary
82                 if (!designation.equalsIgnoreCase(motor.getDesignation().trim())) {
83                         designation = simplifiedDesignation;
84                 }
85                 
86                 // Add the standard delays
87                 for (double d : motor.getStandardDelays()) {
88                         d = Math.rint(d);
89                         if (!delays.contains(d)) {
90                                 delays.add(d);
91                         }
92                 }
93                 Collections.sort(delays);
94                 
95
96                 // Check whether to add as new motor or overwrite existing
97                 final String digest = motor.getDigest();
98                 for (int index = 0; index < motors.size(); index++) {
99                         Motor m = motors.get(index);
100                         
101                         if (digest.equals(digestMap.get(m)) &&
102                                         motor.getDesignation().equals(m.getDesignation())) {
103                                 
104                                 // Match found, check which one to keep (or both) based on comment
105                                 String newCmt = motor.getDescription().replaceAll("\\s+", " ").trim();
106                                 String oldCmt = m.getDescription().replaceAll("\\s+", " ").trim();
107                                 
108                                 if (newCmt.length() == 0 || newCmt.equals(oldCmt)) {
109                                         // Do not replace and do not add
110                                         return;
111                                 } else if (oldCmt.length() == 0) {
112                                         // Replace existing motor
113                                         motors.set(index, motor);
114                                         digestMap.put(motor, digest);
115                                         return;
116                                 }
117                                 // else continue search and add both
118                                 
119                         }
120                 }
121                 
122                 // Motor not present, add it
123                 motors.add(motor);
124                 digestMap.put(motor, digest);
125                 Collections.sort(motors, comparator);
126                 
127         }
128         
129         
130         public boolean matches(ThrustCurveMotor m) {
131                 if (motors.isEmpty())
132                         return true;
133                 
134                 if (manufacturer != m.getManufacturer())
135                         return false;
136                 
137                 if (!MathUtil.equals(diameter, m.getDiameter()))
138                         return false;
139                 
140                 if (!MathUtil.equals(length, m.getLength()))
141                         return false;
142                 
143                 if ((type != Type.UNKNOWN) && (m.getMotorType() != Type.UNKNOWN) &&
144                                 (type != m.getMotorType())) {
145                         return false;
146                 }
147                 
148                 if (!simplifiedDesignation.equals(simplifyDesignation(m.getDesignation())))
149                         return false;
150                 
151                 return true;
152         }
153         
154         
155         public List<ThrustCurveMotor> getMotors() {
156                 return motors.clone();
157         }
158         
159         
160         public int getMotorCount() {
161                 return motors.size();
162         }
163         
164         
165         /**
166          * Return the standard delays applicable to this motor type.  This is a union of
167          * all the delays of the motors included in this set.
168          * @return the delays
169          */
170         public List<Double> getDelays() {
171                 return Collections.unmodifiableList(delays);
172         }
173         
174         
175         /**
176          * Return the manufacturer of this motor type.
177          * @return the manufacturer
178          */
179         public Manufacturer getManufacturer() {
180                 return manufacturer;
181         }
182         
183         
184         /**
185          * Return the designation of this motor type.  This is either the exact or simplified
186          * designation, depending on what motors have been added.
187          * @return the designation
188          */
189         public String getDesignation() {
190                 return designation;
191         }
192         
193         
194         /**
195          * Return the diameter of this motor type.
196          * @return the diameter
197          */
198         public double getDiameter() {
199                 return diameter;
200         }
201         
202         
203         /**
204          * Return the length of this motor type.
205          * @return the length
206          */
207         public double getLength() {
208                 return length;
209         }
210         
211         
212         /**
213          * Return the type of this motor type.  If any of the added motors has a type different
214          * from UNKNOWN, then that type will be returned.
215          * @return the type
216          */
217         public Motor.Type getType() {
218                 return type;
219         }
220         
221         
222
223
224         @Override
225         public String toString() {
226                 return "ThrustCurveMotorSet[" + manufacturer + " " + designation +
227                                 ", type=" + type + ", count=" + motors.size() + "]";
228         }
229         
230         
231
232
233         private static final Pattern SIMPLIFY_PATTERN = Pattern.compile("^[0-9]*[ -]*([A-Z][0-9]+).*");
234         
235         /**
236          * Simplify a motor designation, if possible.  This attempts to reduce the designation
237          * into a simple letter + number notation for the impulse class and average thrust.
238          * 
239          * @param str   the designation to simplify
240          * @return              the simplified designation, or the string itself if the format was not detected
241          */
242         public static String simplifyDesignation(String str) {
243                 str = str.trim();
244                 Matcher m = SIMPLIFY_PATTERN.matcher(str);
245                 if (m.matches()) {
246                         return m.group(1);
247                 } else {
248                         return str;
249                 }
250         }
251         
252         
253         /**
254          * Comparator for deciding in which order to display matching motors.
255          */
256         private static class ThrustCurveMotorComparator implements Comparator<ThrustCurveMotor> {
257                 
258                 @Override
259                 public int compare(ThrustCurveMotor o1, ThrustCurveMotor o2) {
260                         // 1. Designation
261                         if (!o1.getDesignation().equals(o2.getDesignation())) {
262                                 return o1.getDesignation().compareTo(o2.getDesignation());
263                         }
264                         
265                         // 2. Number of data points (more is better)
266                         if (o1.getTimePoints().length != o2.getTimePoints().length) {
267                                 return o2.getTimePoints().length - o1.getTimePoints().length;
268                         }
269                         
270                         // 3. Comment length (longer is better)
271                         return o2.getDescription().length() - o1.getDescription().length();
272                 }
273                 
274         }
275         
276         
277         @Override
278         public int compareTo(ThrustCurveMotorSet other) {
279                 
280                 int value;
281                 
282                 // 1. Manufacturer
283                 value = COLLATOR.compare(this.manufacturer.getDisplayName(),
284                                 other.manufacturer.getDisplayName());
285                 if (value != 0)
286                         return value;
287                 
288                 // 2. Designation
289                 value = DESIGNATION_COMPARATOR.compare(this.designation, other.designation);
290                 if (value != 0)
291                         return value;
292                 
293                 // 3. Diameter
294                 value = (int) ((this.diameter - other.diameter) * 1000000);
295                 if (value != 0)
296                         return value;
297                 
298                 // 4. Length
299                 value = (int) ((this.length - other.length) * 1000000);
300                 return value;
301                 
302         }
303         
304 }