/**
* Copyright (C) 2010-14 diirt developers. See COPYRIGHT.TXT
* All rights reserved. Use is subject to license terms. See LICENSE.TXT
*/
package org.diirt.datasource.sim;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
import java.time.Duration;
import java.time.Instant;
/**
* Value object for replay function. Adds introspection utilities to substitute
* values from another ReplayValue.
* <p>
* Classes extending this class must include a default constructor.
*
* @author carcassi
*/
class ReplayValue {
// Introspected fields for ReplyValue classes
private static Map<Class<?>, List<Field>> fields = new ConcurrentHashMap<Class<?>, List<Field>>();
private static final Logger log = Logger.getLogger(ReplayValue.class.getName());
// TimeStamp support
@XmlAttribute @XmlJavaTypeAdapter(value=XmlTimeStampAdapter.class)
Instant timeStamp;
public Instant getTimestamp() {
return timeStamp;
}
/**
* Uses reflection to determine the non-static fields.
*
* @param clazz a ReplayValue class
* @param props list of properties to add
* @return the props argument, filled with all fields
*/
private static List<Field> calculateFields(Class<?> clazz, List<Field> props) {
for (Field field : clazz.getDeclaredFields()) {
if (!Modifier.isStatic(field.getModifiers())) {
props.add(field);
}
}
if (clazz.getSuperclass() != null) {
calculateFields(clazz.getSuperclass(), props);
}
return props;
}
/**
* The non-static fields. Uses reflection the first time, then uses the
* cached values.
*
* @return a list of fields
*/
private List<Field> properties() {
List<Field> props = fields.get(getClass());
if (props == null) {
props = calculateFields(getClass(), new ArrayList<Field>());
fields.put(getClass(), props);
}
return props;
}
/**
* Returns a new ReplayObject with the same data.
*
* @return a copy of this object
*/
ReplayValue copy() {
try {
ReplayValue copy = getClass().newInstance();
copy.updateValue(this);
return copy;
} catch (InstantiationException ex) {
log.log(Level.SEVERE, null, ex);
throw new RuntimeException("Can't copy ReplayValue", ex);
} catch (IllegalAccessException ex) {
log.log(Level.SEVERE, null, ex);
throw new RuntimeException("Can't copy ReplayValue", ex);
}
}
/**
* Changes the time by adding the given duration.
*
* @param duration a time duration
*/
void adjustTime(Duration duration) {
timeStamp = timeStamp.plus(duration);
}
/**
* Updates all fields with the values found in the argument's fields.
*
* @param obj another value of the same type
*/
void updateValue(ReplayValue obj) {
if (!getClass().isInstance(obj)) {
throw new RuntimeException("Updating value " + this + " from different class " + obj);
}
for (Field field : properties()) {
try {
Object newValue = field.get(obj);
if (newValue != null) {
field.set(this, newValue);
}
} catch (IllegalAccessException ex) {
throw new RuntimeException("Field " + field + " is not accessible", ex);
} catch (IllegalArgumentException ex) {
throw new RuntimeException("Field " + field + " had an inconsistent value", ex);
}
}
}
/**
* Updates fields that currently have null values with the values found
* in the argument's fields.
*
* @param obj another value of the same type
*/
void updateNullValues(ReplayValue obj) {
if (!getClass().isInstance(obj)) {
throw new RuntimeException("Updating value " + this + " from different class " + obj);
}
for (Field field : properties()) {
try {
Object oldValue = field.get(this);
if (oldValue == null) {
field.set(this, field.get(obj));
}
} catch (IllegalAccessException ex) {
throw new RuntimeException("Field " + field + " is not accessible", ex);
} catch (IllegalArgumentException ex) {
throw new RuntimeException("Field " + field + " had an inconsistent value", ex);
}
}
}
}