create changelog entry
[debian/openrocket] / core / src / net / sf / openrocket / motor / MotorDigest.java
1 package net.sf.openrocket.motor;
2
3 import java.io.UnsupportedEncodingException;
4 import java.security.MessageDigest;
5 import java.security.NoSuchAlgorithmException;
6
7 import net.sf.openrocket.util.Coordinate;
8 import net.sf.openrocket.util.TextUtil;
9
10 /**
11  * A class that generated a "digest" of a motor.  A digest is a string value that
12  * uniquely identifies a motor (like a hash code or checksum).  Two motors that have
13  * the same digest behave similarly with a very high probability.  The digest can
14  * therefore be used to identify motors that otherwise have the same specifications.
15  * <p>
16  * The digest only uses a limited amount of precision, so that rounding errors won't
17  * cause differing digest results.
18  * 
19  * @author Sampo Niskanen <sampo.niskanen@iki.fi>
20  */
21 public class MotorDigest {
22         
23         private static final double EPSILON = 0.00000000001;
24         
25         public enum DataType {
26                 /** An array of time points at which data is available (in ms) */
27                 TIME_ARRAY(0, 1000),
28                 /** Mass data for a few specific points (normally initial and empty mass) (in 0.1g) */
29                 MASS_SPECIFIC(1, 10000),
30                 /** Mass per time (in 0.1g) */
31                 MASS_PER_TIME(2, 10000),
32                 /** CG position for a few specific points (normally initial and final CG) (in mm) */
33                 CG_SPECIFIC(3, 1000),
34                 /** CG position per time (in mm) */
35                 CG_PER_TIME(4, 1000),
36                 /** Thrust force per time (in mN) */
37                 FORCE_PER_TIME(5, 1000);
38                 
39                 private final int order;
40                 private final int multiplier;
41                 
42                 DataType(int order, int multiplier) {
43                         this.order = order;
44                         this.multiplier = multiplier;
45                 }
46                 
47                 public int getOrder() {
48                         return order;
49                 }
50                 
51                 public int getMultiplier() {
52                         return multiplier;
53                 }
54         }
55         
56         
57         private final MessageDigest digest;
58         private boolean used = false;
59         private int lastOrder = -1;
60         
61         
62         public MotorDigest() {
63                 try {
64                         digest = MessageDigest.getInstance("MD5");
65                 } catch (NoSuchAlgorithmException e) {
66                         throw new IllegalStateException("MD5 digest not supported by JRE", e);
67                 }
68         }
69         
70         
71         
72         public void update(DataType type, double... values) {
73                 int multiplier = type.getMultiplier();
74                 
75                 int[] intValues = new int[values.length];
76                 for (int i = 0; i < values.length; i++) {
77                         double v = values[i];
78                         v = next(v);
79                         v *= multiplier;
80                         v = next(v);
81                         intValues[i] = (int) Math.round(v);
82                 }
83                 update(type, intValues);
84         }
85         
86         
87         private void update(DataType type, int... values) {
88                 
89                 // Check for correct order
90                 if (lastOrder >= type.getOrder()) {
91                         throw new IllegalArgumentException("Called with type=" + type + " order=" + type.getOrder() +
92                                         " while lastOrder=" + lastOrder);
93                 }
94                 lastOrder = type.getOrder();
95                 
96                 // Digest the type
97                 digest.update(bytes(type.getOrder()));
98                 
99                 // Digest the data length
100                 digest.update(bytes(values.length));
101                 
102                 // Digest the values
103                 for (int v : values) {
104                         digest.update(bytes(v));
105                 }
106                 
107         }
108         
109         
110         private static double next(double v) {
111                 return v + Math.signum(v) * EPSILON;
112         }
113         
114         
115         public String getDigest() {
116                 if (used) {
117                         throw new IllegalStateException("MotorDigest already used");
118                 }
119                 used = true;
120                 byte[] result = digest.digest();
121                 return TextUtil.hexString(result);
122         }
123         
124         
125         
126         private byte[] bytes(int value) {
127                 return new byte[] {
128                                 (byte) ((value >>> 24) & 0xFF), (byte) ((value >>> 16) & 0xFF),
129                                 (byte) ((value >>> 8) & 0xFF), (byte) (value & 0xFF) };
130         }
131         
132         
133         /**
134          * Digest the contents of a thrust curve motor.  The result is a string uniquely
135          * defining the functional aspects of the motor.
136          * 
137          * @param m             the motor to digest
138          * @return              the digest
139          */
140         public static String digestMotor(ThrustCurveMotor m) {
141                 
142                 // Create the motor digest from data available in RASP files
143                 MotorDigest motorDigest = new MotorDigest();
144                 motorDigest.update(DataType.TIME_ARRAY, m.getTimePoints());
145                 
146                 Coordinate[] cg = m.getCGPoints();
147                 double[] cgx = new double[cg.length];
148                 double[] mass = new double[cg.length];
149                 for (int i = 0; i < cg.length; i++) {
150                         cgx[i] = cg[i].x;
151                         mass[i] = cg[i].weight;
152                 }
153                 
154                 motorDigest.update(DataType.MASS_PER_TIME, mass);
155                 motorDigest.update(DataType.CG_PER_TIME, cgx);
156                 motorDigest.update(DataType.FORCE_PER_TIME, m.getThrustPoints());
157                 return motorDigest.getDigest();
158                 
159         }
160         
161         public static String digestComment(String comment) {
162                 comment = comment.replaceAll("\\s+", " ").trim();
163                 
164                 MessageDigest digest;
165                 try {
166                         digest = MessageDigest.getInstance("MD5");
167                 } catch (NoSuchAlgorithmException e) {
168                         throw new IllegalStateException("MD5 digest not supported by JRE", e);
169                 }
170                 
171                 try {
172                         digest.update(comment.getBytes("UTF-8"));
173                 } catch (UnsupportedEncodingException e) {
174                         throw new IllegalStateException("UTF-8 encoding not supported by JRE", e);
175                 }
176                 
177                 return TextUtil.hexString(digest.digest());
178         }
179         
180 }