package net.sf.openrocket.motor;
import java.io.UnsupportedEncodingException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import net.sf.openrocket.util.Coordinate;
import net.sf.openrocket.util.TextUtil;
/**
* A class that generated a "digest" of a motor. A digest is a string value that
* uniquely identifies a motor (like a hash code or checksum). Two motors that have
* the same digest behave similarly with a very high probability. The digest can
* therefore be used to identify motors that otherwise have the same specifications.
* <p>
* The digest only uses a limited amount of precision, so that rounding errors won't
* cause differing digest results.
*
* @author Sampo Niskanen <sampo.niskanen@iki.fi>
*/
public class MotorDigest {
private static final double EPSILON = 0.00000000001;
public enum DataType {
/** An array of time points at which data is available (in ms) */
TIME_ARRAY(0, 1000),
/** Mass data for a few specific points (normally initial and empty mass) (in 0.1g) */
MASS_SPECIFIC(1, 10000),
/** Mass per time (in 0.1g) */
MASS_PER_TIME(2, 10000),
/** CG position for a few specific points (normally initial and final CG) (in mm) */
CG_SPECIFIC(3, 1000),
/** CG position per time (in mm) */
CG_PER_TIME(4, 1000),
/** Thrust force per time (in mN) */
FORCE_PER_TIME(5, 1000);
private final int order;
private final int multiplier;
DataType(int order, int multiplier) {
this.order = order;
this.multiplier = multiplier;
}
public int getOrder() {
return order;
}
public int getMultiplier() {
return multiplier;
}
}
private final MessageDigest digest;
private boolean used = false;
private int lastOrder = -1;
public MotorDigest() {
try {
digest = MessageDigest.getInstance("MD5");
} catch (NoSuchAlgorithmException e) {
throw new IllegalStateException("MD5 digest not supported by JRE", e);
}
}
public void update(DataType type, double... values) {
int multiplier = type.getMultiplier();
int[] intValues = new int[values.length];
for (int i = 0; i < values.length; i++) {
double v = values[i];
v = next(v);
v *= multiplier;
v = next(v);
intValues[i] = (int) Math.round(v);
}
update(type, intValues);
}
private void update(DataType type, int... values) {
// Check for correct order
if (lastOrder >= type.getOrder()) {
throw new IllegalArgumentException("Called with type=" + type + " order=" + type.getOrder() +
" while lastOrder=" + lastOrder);
}
lastOrder = type.getOrder();
// Digest the type
digest.update(bytes(type.getOrder()));
// Digest the data length
digest.update(bytes(values.length));
// Digest the values
for (int v : values) {
digest.update(bytes(v));
}
}
private static double next(double v) {
return v + Math.signum(v) * EPSILON;
}
public String getDigest() {
if (used) {
throw new IllegalStateException("MotorDigest already used");
}
used = true;
byte[] result = digest.digest();
return TextUtil.hexString(result);
}
private byte[] bytes(int value) {
return new byte[] {
(byte) ((value >>> 24) & 0xFF), (byte) ((value >>> 16) & 0xFF),
(byte) ((value >>> 8) & 0xFF), (byte) (value & 0xFF) };
}
/**
* Digest the contents of a thrust curve motor. The result is a string uniquely
* defining the functional aspects of the motor.
*
* @param m the motor to digest
* @return the digest
*/
public static String digestMotor(ThrustCurveMotor m) {
// Create the motor digest from data available in RASP files
MotorDigest motorDigest = new MotorDigest();
motorDigest.update(DataType.TIME_ARRAY, m.getTimePoints());
Coordinate[] cg = m.getCGPoints();
double[] cgx = new double[cg.length];
double[] mass = new double[cg.length];
for (int i = 0; i < cg.length; i++) {
cgx[i] = cg[i].x;
mass[i] = cg[i].weight;
}
motorDigest.update(DataType.MASS_PER_TIME, mass);
motorDigest.update(DataType.CG_PER_TIME, cgx);
motorDigest.update(DataType.FORCE_PER_TIME, m.getThrustPoints());
return motorDigest.getDigest();
}
public static String digestComment(String comment) {
comment = comment.replaceAll("\\s+", " ").trim();
MessageDigest digest;
try {
digest = MessageDigest.getInstance("MD5");
} catch (NoSuchAlgorithmException e) {
throw new IllegalStateException("MD5 digest not supported by JRE", e);
}
try {
digest.update(comment.getBytes("UTF-8"));
} catch (UnsupportedEncodingException e) {
throw new IllegalStateException("UTF-8 encoding not supported by JRE", e);
}
return TextUtil.hexString(digest.digest());
}
}