Java tutorial
/* * Copyright (C) 2014-2015 ULYSSIS VZW * * This file is part of i++. * * i++ is free software: you can redistribute it and/or modify * it under the terms of version 3 of the GNU Affero General Public License * as published by the Free Software Foundation. No other versions apply. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <http://www.gnu.org/licenses/> */ package org.ulyssis.ipp.snapshot; import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.core.ObjectCodec; import com.fasterxml.jackson.databind.DeserializationContext; import com.fasterxml.jackson.databind.JsonDeserializer; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.JsonSerializer; import com.fasterxml.jackson.databind.SerializerProvider; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.fasterxml.jackson.databind.annotation.JsonSerialize; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.ulyssis.ipp.config.Config; import org.ulyssis.ipp.config.ReaderConfig; import java.io.IOException; import java.time.Duration; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Optional; @JsonSerialize(using = TeamState.Serializer.class) @JsonDeserialize(using = TeamState.Deserializer.class) public final class TeamState { private static final Logger LOG = LogManager.getLogger(TeamState.class); // TODO: Make these configurable! private static final double ALPHA = 0.4; private static final long MIN_TIME_BETWEEN_UPDATES = 30L; static class Serializer extends JsonSerializer<TeamState> { @Override public void serialize(TeamState value, JsonGenerator jgen, SerializerProvider provider) throws IOException { jgen.writeStartObject(); if (value.lastTagSeenEvent.isPresent()) { jgen.writeFieldName("lastTagSeenEvent"); jgen.writeObject(value.lastTagSeenEvent.get()); } jgen.writeFieldName("tagFragmentCount"); jgen.writeNumber(value.tagFragmentCount); if (!Double.isNaN(value.speed)) { jgen.writeFieldName("speed"); jgen.writeNumber(value.speed); } if (!Double.isNaN(value.predictedSpeed)) { jgen.writeFieldName("predictedSpeed"); jgen.writeNumber(value.predictedSpeed); } jgen.writeEndObject(); } } static class Deserializer extends JsonDeserializer<TeamState> { @Override public TeamState deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException { ObjectCodec oc = jp.getCodec(); JsonNode node = oc.readTree(jp); Iterator<Map.Entry<String, JsonNode>> it = node.fields(); Optional<TagSeenEvent> lastTagSeenEvent = Optional.empty(); int tagFragmentCount = 0; double speed = Double.NaN; double predictedSpeed = Double.NaN; while (it.hasNext()) { Map.Entry<String, JsonNode> entry = it.next(); if (entry.getKey().equals("lastTagSeenEvent")) { TagSeenEvent event = oc.treeAsTokens(entry.getValue()).readValueAs(TagSeenEvent.class); lastTagSeenEvent = Optional.of(event); } else if (entry.getKey().equals("tagFragmentCount")) { tagFragmentCount = oc.treeAsTokens(entry.getValue()).getIntValue(); } else if (entry.getKey().equals("speed")) { speed = oc.treeAsTokens(entry.getValue()).getDoubleValue(); } else if (entry.getKey().equals("predictedSpeed")) { predictedSpeed = oc.treeAsTokens(entry.getValue()).getDoubleValue(); } } return new TeamState(lastTagSeenEvent, tagFragmentCount, speed, predictedSpeed); } } private final Optional<TagSeenEvent> lastTagSeenEvent; // The number of fragments that has been run for this team. // A track is divided into a number of fragments equal to the // number of readers. private final int tagFragmentCount; private final double speed; private final double predictedSpeed; public TeamState() { this.lastTagSeenEvent = Optional.empty(); this.tagFragmentCount = 0; this.speed = Double.NaN; this.predictedSpeed = Double.NaN; } private TeamState(Optional<TagSeenEvent> lastTagSeenEvent, int tagFragmentCount, double speed, double predictedSpeed) { this.lastTagSeenEvent = lastTagSeenEvent; this.tagFragmentCount = tagFragmentCount; this.speed = speed; this.predictedSpeed = predictedSpeed; } // TODO: Refactor! public TeamState addTagSeenEvent(Snapshot snapshot, TagSeenEvent event) { int newTagFragmentCount = tagFragmentCount; double newSpeed = Double.NaN; double newPredictedSpeed = Double.NaN; int lastEventId = 0; if (lastTagSeenEvent.isPresent()) { TagSeenEvent lastEvent = lastTagSeenEvent.get(); if (lastEvent.getReaderId() == event.getReaderId() && Duration.between(lastEvent.getTime(), event.getTime()).minusSeconds(MIN_TIME_BETWEEN_UPDATES) .isNegative()) { LOG.info("Rejecting event because a tag for this team passed less than {} seconds ago", MIN_TIME_BETWEEN_UPDATES); return this; } lastEventId = lastEvent.getReaderId(); } int diff = (event.getReaderId() - lastEventId); if (diff < 0) { diff = Config.getCurrentConfig().getNbReaders() + diff; } else if (diff == 0 && (lastTagSeenEvent.isPresent() || // TODO: Refactor this fustercluck of comparisons (snapshot.getStartTime().isBefore(event.getTime()) && !Duration.between(snapshot.getStartTime(), event.getTime()) .minusSeconds(MIN_TIME_BETWEEN_UPDATES).isNegative()))) { diff = Config.getCurrentConfig().getNbReaders(); } newTagFragmentCount += diff; List<ReaderConfig> readers = Config.getCurrentConfig().getReaders(); double distance = 0; if (lastTagSeenEvent.isPresent()) { for (int i = tagFragmentCount; i < newTagFragmentCount; i++) { int j = i % Config.getCurrentConfig().getNbReaders(); int k = (i + 1) % Config.getCurrentConfig().getNbReaders(); if (k > j) { distance += readers.get(k).getPosition() - readers.get(j).getPosition(); } else if (j > k) { assert k == 0; distance += Config.getCurrentConfig().getTrackLength() - readers.get(j).getPosition(); } else { // This can happen when there is only one reader. distance += Config.getCurrentConfig().getTrackLength(); } } double time = Duration.between(lastTagSeenEvent.get().getTime(), event.getTime()).toMillis() / 1000D; newSpeed = distance / time; if (Double.isNaN(predictedSpeed)) { newPredictedSpeed = newSpeed; } else { newPredictedSpeed = newSpeed * ALPHA + predictedSpeed * (1 - ALPHA); } } else if (snapshot.getStartTime().isBefore(event.getTime()) && !Duration.between(snapshot.getStartTime(), event.getTime()) .minusSeconds(MIN_TIME_BETWEEN_UPDATES).isNegative()) { double time = Duration.between(snapshot.getStartTime(), event.getTime()).toMillis() / 1000D; distance = readers.get(event.getReaderId()).getPosition(); newSpeed = distance / time; if (Double.isNaN(predictedSpeed)) { newPredictedSpeed = newSpeed; } else { newPredictedSpeed = newSpeed * ALPHA + predictedSpeed * (1 - ALPHA); } } return new TeamState(Optional.of(event), newTagFragmentCount, newSpeed, newPredictedSpeed); } public TeamState addCorrection(int correction) { int newTagFragmentCount = tagFragmentCount + correction * Config.getCurrentConfig().getNbReaders(); // TODO: Should we lower bound it to 0? if (newTagFragmentCount < 0) { newTagFragmentCount = 0; } // TODO: I don't know about the prediction, if something should happen to that. return new TeamState(lastTagSeenEvent, newTagFragmentCount, speed, predictedSpeed); } public int getTagFragmentCount() { return tagFragmentCount; } public Optional<TagSeenEvent> getLastTagSeenEvent() { return lastTagSeenEvent; } public int getNbLaps() { return tagFragmentCount / Config.getCurrentConfig().getNbReaders(); } public double getSpeed() { return speed; } public double getPredictedSpeed() { return predictedSpeed; } }