001package jmri.jmrix.dccpp.swing.messages;
002
003import java.util.ArrayDeque;
004import java.util.ArrayList;
005import java.util.Collections;
006import java.util.Deque;
007import java.util.List;
008import java.util.function.LongSupplier;
009
010/** Timestamped DCC-EX messages, newest first, with expiry and a line limit; no Swing, thread-safe. */
011public class MessageQueue {
012
013    public static final int DEFAULT_EXPIRY_SECONDS = 30;
014    public static final int DEFAULT_MAX_LINES = 10;
015
016    private final LongSupplier clock;
017    private final Deque<Entry> entries = new ArrayDeque<>();
018    private int expirySeconds = DEFAULT_EXPIRY_SECONDS;
019    private int maxLines = DEFAULT_MAX_LINES;
020
021    public MessageQueue() {
022        this(System::currentTimeMillis);
023    }
024
025    public MessageQueue(LongSupplier clock) {
026        this.clock = clock;
027    }
028
029    public synchronized void add(String text) {
030        entries.addFirst(new Entry(clock.getAsLong(), text));
031        trim();
032    }
033
034    /** Unmodifiable copy of the messages, newest first. */
035    public synchronized List<Entry> getEntries() {
036        return Collections.unmodifiableList(new ArrayList<>(entries));
037    }
038
039    /** Remove messages older than the expiry time; true if any were removed. */
040    public synchronized boolean expire() {
041        if (expirySeconds <= 0) {
042            return false;
043        }
044        long cutoff = clock.getAsLong() - expirySeconds * 1000L;
045        boolean removed = false;
046        while (!entries.isEmpty() && entries.peekLast().getTimeMillis() <= cutoff) {
047            entries.removeLast();
048            removed = true;
049        }
050        return removed;
051    }
052
053    public synchronized void clear() {
054        entries.clear();
055    }
056
057    /** Seconds a message is kept; 0 keeps it until newer messages push it out. */
058    public synchronized void setExpirySeconds(int seconds) {
059        expirySeconds = Math.max(0, seconds);
060    }
061
062    public synchronized int getExpirySeconds() {
063        return expirySeconds;
064    }
065
066    public synchronized void setMaxLines(int lines) {
067        maxLines = Math.max(1, lines);
068        trim();
069    }
070
071    public synchronized int getMaxLines() {
072        return maxLines;
073    }
074
075    private void trim() {
076        while (entries.size() > maxLines) {
077            entries.removeLast();
078        }
079    }
080
081    /** One received message. */
082    public static final class Entry {
083
084        private final long timeMillis;
085        private final String text;
086
087        Entry(long timeMillis, String text) {
088            this.timeMillis = timeMillis;
089            this.text = text;
090        }
091
092        public long getTimeMillis() {
093            return timeMillis;
094        }
095
096        public String getText() {
097            return text;
098        }
099    }
100
101}