package com.openxc.messages;
import com.google.common.base.Objects;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import android.os.Parcel;
import android.os.Parcelable;
import android.util.Log;
import com.google.common.base.MoreObjects;
import com.google.gson.annotations.SerializedName;
/**
* The VehicleMessage is the most basic, root form of data going back and forth
* between the OpenXC library and a vehicle interface. The VI's data stream is
* message based, where each message is a subtype of a VehicleMessage.
*
* This base class implements Parcelable, so it can be sent over an AIDL
* interface, i.e. between the singleton VehicleService and the VehicleManager
* instance in each app's process.
*/
public class VehicleMessage implements Parcelable, Comparable<VehicleMessage> {
private static final String TAG = "VehicleMessage";
public interface Listener {
/* Public: Receive an incoming VehicleMessage.
*/
public void receive(VehicleMessage message);
}
/**
* The field names for the timestamp and extra data, defined in the OpenXC
* Message Format specification.
*/
private static final String TIMESTAMP_KEY = "timestamp";
public static final String EXTRAS_KEY = "extras";
@SerializedName(TIMESTAMP_KEY)
// This is a bit of hack to be able to serialize timestamps to JSON as
// seconds with floating point precision, but represent them internally as
// longs (milliseconds) for performance.
private Double mTimestampSeconds;
private transient Long mTimestamp;
@SerializedName(EXTRAS_KEY)
private Map<String, Object> mExtras;
public VehicleMessage() { }
/**
* Construct a new empty VehicleMessage.
*
* @param timestamp timestamp as milliseconds since unix epoch
*/
public VehicleMessage(Long timestamp) {
setTimestamp(timestamp);
}
/**
* Construct a new VehicleMessage with the given extra data and an
* overridden timestamp.
*/
public VehicleMessage(Long timestamp, Map<String, Object> extras) {
this(extras);
setTimestamp(timestamp);
}
/**
* Construct a new VehicleMessage with the given extra data.
*
* The extras field can hold any arbitrary key/value pairs.
*
* @param extras A map of any extra data to attach to this message.
*/
public VehicleMessage(Map<String, Object> extras) {
setExtras(extras);
}
/**
* Override the timestamp of the message.
*
* @param timestamp the timestamp to set for this message.
*/
public void setTimestamp(Long timestamp) {
if(timestamp != null) {
mTimestamp = timestamp;
mTimestampSeconds = timestamp / 1000.0;
}
}
/**
* @return true if the message has a valid timestamp.
*/
public boolean isTimestamped() {
return getTimestamp() != null;
}
/**
* @return the timestamp of the message in milliseconds since the UNIX
* epoch.
*/
public Long getTimestamp() {
if(mTimestampSeconds != null) {
mTimestamp = Double.valueOf(mTimestampSeconds * 1000).longValue();
}
return mTimestamp;
}
public Date getDate() {
if (!isTimestamped()) {
return null;
}
return new Date(getTimestamp());
}
public void setExtras(Map<String, Object> extras) {
if(extras != null && !extras.isEmpty()) {
mExtras = new HashMap<>(extras);
}
}
public boolean hasExtras() {
return mExtras != null;
}
public Map<String, Object> getExtras() {
return mExtras;
}
/**
* Make the message's timestamp invalid so it won't end up in the
* serialized version.
*/
public void untimestamp() {
mTimestamp = null;
mTimestampSeconds = null;
}
public void timestamp() {
if(!isTimestamped()) {
mTimestamp = System.currentTimeMillis();
}
}
public NamedVehicleMessage asNamedMessage() {
return (NamedVehicleMessage) this;
}
public SimpleVehicleMessage asSimpleMessage() {
return (SimpleVehicleMessage) this;
}
public EventedSimpleVehicleMessage asEventedMessage() {
return (EventedSimpleVehicleMessage) this;
}
public CanMessage asCanMessage() {
return (CanMessage) this;
}
public CommandResponse asCommandResponse() {
return (CommandResponse) this;
}
public DiagnosticRequest asDiagnosticRequest() {
return (DiagnosticRequest) this;
}
public DiagnosticResponse asDiagnosticResponse() {
return (DiagnosticResponse) this;
}
public KeyedMessage asKeyedMessage() {
return (KeyedMessage) this;
}
@Override
public String toString() {
return MoreObjects.toStringHelper(this)
.add("timestamp", getTimestamp())
.add("extras", getExtras())
.toString();
}
@Override
public int describeContents() {
return 0;
}
public int compareTo(VehicleMessage other) {
return getTimestamp().compareTo(other.getTimestamp());
}
@Override
public boolean equals(Object obj) {
if(obj == null) {
return false;
}
if(this == obj) {
return true;
}
final VehicleMessage other = (VehicleMessage) obj;
return Objects.equal(getTimestamp(), other.getTimestamp()) &&
Objects.equal(mExtras, other.mExtras);
}
@Override
public void writeToParcel(Parcel out, int flags) {
out.writeString(getClass().getName());
out.writeValue(getTimestamp());
out.writeValue(getExtras());
}
protected void readFromParcel(Parcel in) {
// Not reading the derived class name as it is already pulled out of the
// Parcel by the CREATOR.
setTimestamp((Long)in.readValue(Long.class.getClassLoader()));
//noinspection unchecked
mExtras = (HashMap<String, Object>) in.readValue(
HashMap.class.getClassLoader());
}
public static final Parcelable.Creator<VehicleMessage> CREATOR =
new Parcelable.Creator<VehicleMessage>() {
@Override
public VehicleMessage createFromParcel(Parcel in) {
String messageClassName = in.readString();
Constructor<? extends VehicleMessage> constructor;
Class<? extends VehicleMessage> messageClass = null;
try {
try {
messageClass = Class.forName(messageClassName).asSubclass(
VehicleMessage.class);
} catch(ClassNotFoundException e) {
throw new UnrecognizedMessageTypeException(
"Unrecognized message class: " + messageClassName);
}
try {
// Must use getDeclaredConstructor because it's a protected
// constructor. That's OK since we are the parent class and
// should have access, we're not breaking abstraction.
constructor = messageClass.getDeclaredConstructor(Parcel.class);
} catch(NoSuchMethodException e) {
throw new UnrecognizedMessageTypeException(messageClass +
" doesn't have the expected constructor", e);
}
return constructor.newInstance(in);
} catch(InstantiationException|IllegalAccessException
|InvocationTargetException
|UnrecognizedMessageTypeException e) {
Log.e(TAG, "Unable to unparcel a " + messageClass, e);
return new VehicleMessage();
}
}
@Override
public VehicleMessage[] newArray(int size) {
return new VehicleMessage[size];
}
};
// This must be protected so that we can call it using reflection from this
// class. Kind of weird, but it works.
protected VehicleMessage(Parcel in) {
readFromParcel(in);
}
}