package nl.tno.sensorstorm.particlemapper;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
import java.util.Map.Entry;
import java.util.SortedMap;
import nl.tno.sensorstorm.api.particles.Particle;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import backtype.storm.tuple.Fields;
import backtype.storm.tuple.Tuple;
import backtype.storm.tuple.Values;
/**
* The ParticleClassInfo contains information and methods on how to map
* {@link Particle}s without a custom mapper.
*/
public class ParticleClassInfo {
private static Logger log = LoggerFactory
.getLogger(ParticleClassInfo.class);
// key = name of field, value = name in tuple
private final SortedMap<String, String> fields;
private final Fields outputFields;
private final Class<?> clazz;;
/**
* Construct a {@link ParticleClassInfo} for a type of {@link Particle}.
*
* @param clazz
* {@link Class} of this {@link Particle}
* @param fields
* Map with as key the name of the field in the {@link Particle},
* value the name in the {@link Tuple}
*/
public ParticleClassInfo(Class<?> clazz, SortedMap<String, String> fields) {
this.fields = fields;
this.clazz = clazz;
List<String> copy = new ArrayList<>();
copy.add(ParticleMapper.TIMESTAMP_FIELD_NAME);
copy.add(ParticleMapper.PARTICLE_CLASS_FIELD_NAME);
copy.addAll(this.fields.values());
outputFields = new Fields(copy);
}
/**
* Map a {@link Tuple} into a {@link Particle}.
*
* @param <T>
* Type of the {@link Particle}
* @param tuple
* {@link Tuple} to be mapped
* @param clazz
* {@link Class} of the {@link Particle}, should be equal to the
* {@link Class} passed in the constructor
* @return The mapped {@link Particle}
*/
@SuppressWarnings("unchecked")
public <T> T tupleToParticle(Tuple tuple, Class<T> clazz) {
try {
assert clazz.equals(this.clazz);
Class<? extends Particle> particleClass = (Class<? extends Particle>) Class
.forName(tuple.getString(ParticleMapper.PARTICLE_CLASS_IDX));
Particle particle = particleClass.newInstance();
particle.setTimestamp(tuple.getLong(ParticleMapper.TIMESTAMP_IDX));
for (Entry<String, String> e : fields.entrySet()) {
Field declaredField = particleClass
.getDeclaredField(e.getKey());
declaredField.setAccessible(true);
declaredField
.set(particle, tuple.getValueByField(e.getValue()));
}
return (T) particle;
} catch (ClassNotFoundException | IllegalAccessException
| NoSuchFieldException | SecurityException e) {
log.error(
"Was unable to map tuple to particle: " + tuple.toString(),
e);
return null;
} catch (InstantiationException e) {
throw new IllegalArgumentException(
"Particles should always have an empty constructor");
}
}
/**
* Map a {@link Particle} to a {@link Values} object.
*
* @param particle
* {@link Particle} to map
* @return Mapped {@link Particle}
*/
public Values particleToValues(Particle particle) {
try {
Values v = new Values();
// see constant TIMESTAP_IDX
v.add(particle.getTimestamp());
// see constant PARTICLE_CLASS_IDX
v.add(particle.getClass().getName());
for (String field : fields.keySet()) {
Field declaredField = clazz.getDeclaredField(field);
declaredField.setAccessible(true);
v.add(declaredField.get(particle));
}
return v;
} catch (IllegalArgumentException | IllegalAccessException
| NoSuchFieldException | SecurityException e) {
log.error("Was not able to map particle to value", e);
return null;
}
}
/**
* {@link Fields} object describing the {@link Particle}.
*
* @return {@link Fields} with fields the {@link Tuple}
*/
public Fields getFields() {
return outputFields;
}
}