1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
use jni::objects::{JObject, JValue, JString, JList};
use jni::JNIEnv;
use crate::error::Error;


const LONGTYPE:u16 =  'J' as u16;
const INTTYPE:u16 =  'I' as u16;
const SHORTTYPE:u16 =  'S' as u16;
const BYTETYPE:u16 =  'B' as u16;
// const CHARTYPE:u16 =  'C' as u16;
const BOOLEANTYPE:u16 =  'Z' as u16;
const DOUBLETYPE:u16 =  'D' as u16;
const FLOATTYPE:u16 =  'F' as u16;
// const STRINGTYPE:u16 =  's' as u16;
// const OBJECTTYPE:u16 =  'L' as u16;
const ARRAYTYPE:u16 =  'a' as u16;
// const TWODTYPE:u16 =  '2' as u16;
const BINARYTYPE:u16 =  'b' as u16;

/// ValueObject is the generic wrapper for an object returned from MIStudio. It will be a deep copy when created and thus has no reliance on the JVM.
#[derive(Debug, PartialEq, Clone)]
pub struct ValueObject {
    /// The wrapped value
    pub value:Value,
    /// The timestamp when the value was created
    pub timestamp:u64,
    /// A quality indicator
    pub quality:Quality
}

/// Value wrapper for the value contained in a ValueObject
#[derive(Debug, PartialEq, Clone)]
pub enum Value {
    /// A StringValueObject
    String(String),
    /// A LongValueObject, which is also used for other integer types
    Long(i64),
    /// An ArrayValueObject, which is represented by a Vec of ValueObject. This is a deep copy of the referenced values.
    Array(Vec<ValueObject>),
    // /// 
    // TwoDimensional(Vec<Vec<ValueObject>>),
    /// A BinaryValueObject
    Binary(Vec<u8>),
    /// A ValueObject representing a boolean
    Boolean(bool),
    /// A DoubleValueObject, which is also used for single precision floating point types
    Double(f64),
}

/// Quality indicator for a ValueObject
#[derive(Debug, PartialEq, Clone)]
pub enum Quality {
    /// Good quality says that the ValueObject should be Ok to use
    Good,
    /// Bad quality usually means that there was a problem with the ValueObject 
    Bad
}

impl ValueObject {
    pub(crate) fn from_jobject(env:&JNIEnv, jobject:JObject) -> Result<ValueObject, Error> {
        let type_char = env.call_method(jobject, "getType", "()C", &[]);
        if type_char.is_err() {
            return Err(Error::InternalError(format!("Error calling getType on value object: {}", type_char.unwrap_err())))
        }
        match type_char.unwrap() {
            JValue::Char(char) => {
                match char {
                    LONGTYPE | INTTYPE | SHORTTYPE | BYTETYPE => {
                        let value = env.call_method(jobject, "getLongValue", "()J", &[]);
                        if value.is_err() {
                            return Err(Error::InternalError(format!("Error calling getLongValue on value object: {}", value.unwrap_err())))
                        }
                        let value = value.unwrap();
                        let timestamp = env.call_method(jobject, "getTimeMillis", "()J", &[]);
                        if timestamp.is_err() {
                            return Err(Error::InternalError(format!("Error calling getTimeMillis on value object: {}", timestamp.unwrap_err())))
                        }
                        let timestamp = timestamp.unwrap();
                        let quality = env.call_method(jobject, "getQuality", "()I", &[]);
                        if quality.is_err() {
                            return Err(Error::InternalError(format!("Error calling getTimeMillis on value object: {}", quality.unwrap_err())))
                        }
                        let quality = quality.unwrap().i().unwrap();
                        let quality = if quality == 0 {Quality::Good} else {Quality::Bad};
                        return Ok(ValueObject{value:Value::Long(value.j().unwrap()), timestamp:timestamp.j().unwrap() as u64, quality:quality})
                    },
                    DOUBLETYPE | FLOATTYPE => {
                        let value = env.call_method(jobject, "getDoubleValue", "()D", &[]);
                        if value.is_err() {
                            return Err(Error::InternalError(format!("Error calling getDoubleValue on value object: {}", value.unwrap_err())))
                        }
                        let value = value.unwrap();
                        let timestamp = env.call_method(jobject, "getTimeMillis", "()J", &[]);
                        if timestamp.is_err() {
                            return Err(Error::InternalError(format!("Error calling getTimeMillis on value object: {}", timestamp.unwrap_err())))
                        }
                        let timestamp = timestamp.unwrap();
                        let quality = env.call_method(jobject, "getQuality", "()I", &[]);
                        if quality.is_err() {
                            return Err(Error::InternalError(format!("Error calling getTimeMillis on value object: {}", quality.unwrap_err())))
                        }
                        let quality = quality.unwrap().i().unwrap();
                        let quality = if quality == 0 {Quality::Good} else {Quality::Bad};
                        return Ok(ValueObject{value:Value::Double(value.d().unwrap()), timestamp:timestamp.j().unwrap() as u64, quality:quality})
                    },
                    BOOLEANTYPE => {
                        let value = env.call_method(jobject, "getBoolValue", "()Z", &[]);
                        if value.is_err() {
                            return Err(Error::InternalError(format!("Error calling getBoolValue on value object: {}", value.unwrap_err())))
                        }
                        let value = value.unwrap();
                        let timestamp = env.call_method(jobject, "getTimeMillis", "()J", &[]);
                        if timestamp.is_err() {
                            return Err(Error::InternalError(format!("Error calling getTimeMillis on value object: {}", timestamp.unwrap_err())))
                        }
                        let timestamp = timestamp.unwrap();
                        let quality = env.call_method(jobject, "getQuality", "()I", &[]);
                        if quality.is_err() {
                            return Err(Error::InternalError(format!("Error calling getTimeMillis on value object: {}", quality.unwrap_err())))
                        }
                        let quality = quality.unwrap().i().unwrap();
                        let quality = if quality == 0 {Quality::Good} else {Quality::Bad};
                        return Ok(ValueObject{value:Value::Boolean(value.z().unwrap()), timestamp:timestamp.j().unwrap() as u64, quality:quality})
                    },
                    BINARYTYPE => {
                        let value = env.call_method(jobject, "getBinaryValue", "()[B", &[]);
                        if value.is_err() {
                            return Err(Error::InternalError(format!("Error calling getBinaryValue on value object: {}", value.unwrap_err())))
                        }
                        let value = value.unwrap().l().unwrap();
                        let timestamp = env.call_method(jobject, "getTimeMillis", "()J", &[]);
                        if timestamp.is_err() {
                            return Err(Error::InternalError(format!("Error calling getTimeMillis on value object: {}", timestamp.unwrap_err())))
                        }
                        let timestamp = timestamp.unwrap();
                        let quality = env.call_method(jobject, "getQuality", "()I", &[]);
                        if quality.is_err() {
                            return Err(Error::InternalError(format!("Error calling getTimeMillis on value object: {}", quality.unwrap_err())))
                        }
                        let quality = quality.unwrap().i().unwrap();
                        let quality = if quality == 0 {Quality::Good} else {Quality::Bad};
                        return Ok(ValueObject{value:Value::Binary(env.convert_byte_array(*value).unwrap()), timestamp:timestamp.j().unwrap() as u64, quality:quality})
                    },
                    ARRAYTYPE => {
                        let list = JList::from_env(env, jobject);
                        if list.is_err() {
                            return Err(Error::InternalError(format!("Error getting list from value object")))
                        }
                        let list = list.unwrap();
                        let timestamp = env.call_method(jobject, "getTimeMillis", "()J", &[]);
                        if timestamp.is_err() {
                            return Err(Error::InternalError(format!("Error calling getTimeMillis on value object: {}", timestamp.unwrap_err())))
                        }
                        let timestamp = timestamp.unwrap();
                        let quality = env.call_method(jobject, "getQuality", "()I", &[]);
                        if quality.is_err() {
                            return Err(Error::InternalError(format!("Error calling getTimeMillis on value object: {}", quality.unwrap_err())))
                        }
                        let quality = quality.unwrap().i().unwrap();
                        let quality = if quality == 0 {Quality::Good} else {Quality::Bad};
                        let mut vec = Vec::<ValueObject>::with_capacity(list.size().unwrap() as usize);
                        for value in list.iter().unwrap() {
                            let value = ValueObject::from_jobject(env, value);
                            if value.is_err() {
                                return Err(value.unwrap_err());
                            }
                            vec.push(value.unwrap());
                        }
                        return Ok(ValueObject{value:Value::Array(vec), timestamp:timestamp.j().unwrap() as u64, quality:quality})
                    },
                    // TWODTYPE => { // TODO: TWODTYPE

                    // },
                    _ => {
                        let value = env.call_method(jobject, "getStringValue", "()Ljava/lang/String;", &[]);
                        if value.is_err() {
                            return Err(Error::InternalError(format!("Error calling getStringValue on value object: {}", value.unwrap_err())))
                        }
                        let value = value.unwrap();
                        let timestamp = env.call_method(jobject, "getTimeMillis", "()J", &[]);
                        if timestamp.is_err() {
                            return Err(Error::InternalError(format!("Error calling getTimeMillis on value object: {}", timestamp.unwrap_err())))
                        }
                        let timestamp = timestamp.unwrap();
                        let quality = env.call_method(jobject, "getQuality", "()I", &[]);
                        if quality.is_err() {
                            return Err(Error::InternalError(format!("Error calling getTimeMillis on value object: {}", quality.unwrap_err())))
                        }
                        let quality = quality.unwrap().i().unwrap();
                        let quality = if quality == 0 {Quality::Good} else {Quality::Bad};

                        let value = value.l().unwrap();
                        let value = env.get_string(JString::from(value));
                        if value.is_err() {
                            // I think this shouldn't be possible so even though the message is bad it should be ok
                            return Err(Error::InternalError(format!("Error getting string from Java string")));
                        }
                        let value = value.unwrap();
                        let test_value = value.to_str();
                        let string_value;
                        if test_value.is_err() {
                            eprintln!("Warning: string value contained invalid utf8 characters, returning a lossy string");
                            string_value = value.to_string_lossy().to_string();
                        } else {
                            string_value = test_value.unwrap().to_string();
                        }
                        return Ok(ValueObject{value:Value::String(string_value), timestamp:timestamp.j().unwrap() as u64, quality:quality})
                    }
                }
            },
            _=> return Err(Error::InternalError("getType failed to return a char".to_string()))
        }
    }
}