View Javadoc
1   /*
2    * Copyright (C) 2022-2023 Dipl.-Inform. Kai Hofmann. All rights reserved!
3    */
4   package de.powerstat.validation.containers;
5   
6   
7   import java.time.OffsetDateTime;
8   import java.time.format.DateTimeFormatter;
9   import java.util.Map;
10  import java.util.NoSuchElementException;
11  import java.util.Objects;
12  import java.util.Set;
13  import java.util.SortedMap;
14  import java.util.concurrent.ConcurrentSkipListMap;
15  
16  
17  /**
18   * History of a specific type.
19   *
20   * @param <T> Use only value objects
21   *
22   * TODO Change Datetime to an earlier Datetime
23   */
24  public class HistoryOf<T>
25   {
26    /**
27     * History.
28     */
29    private final SortedMap<OffsetDateTime, T> history = new ConcurrentSkipListMap<>();
30  
31  
32    /**
33     * Constructor.
34     */
35    public HistoryOf()
36     {
37      super();
38     }
39  
40  
41    /**
42     * Calculate hash code.
43     *
44     * @return Hash
45     * @see java.lang.Object#hashCode()
46     */
47    @Override
48    public int hashCode()
49     {
50      if (this.history.isEmpty())
51       {
52        return 0;
53       }
54      return Objects.hash(this.getLatestEntry());
55     }
56  
57  
58    /**
59     * Is equal with another object.
60     *
61     * @param obj Object
62     * @return true when equal, false otherwise
63     * @throws NoSuchElementException If there is no entry in this HistoryOf
64     * @see java.lang.Object#equals(java.lang.Object)
65     */
66    @Override
67    public boolean equals(final Object obj)
68     {
69      if (this == obj)
70       {
71        return true;
72       }
73      if (!(obj instanceof HistoryOf<?>))
74      // if ((obj == null) || (this.getClass() != obj.getClass()))
75       {
76        return false;
77       }
78      final HistoryOf<T> other = (HistoryOf<T>)obj;
79      if ((this.history.isEmpty()) || (other.history.isEmpty()))
80       {
81        return ((this.history.isEmpty()) && (other.history.isEmpty()));
82       }
83      return this.getLatestEntry().equals(other.getLatestEntry());
84     }
85  
86  
87    /**
88     * Returns the string representation of this HistoryOf.
89     *
90     * The exact details of this representation are unspecified and subject to change, but the following may be regarded as typical:
91     *
92     * "HistoryOf&lt;&gt;[2020-01-11T21:10:00+01=?, ...]"
93     *
94     * @return String representation of this HistoryOf
95     * @see java.lang.Object#toString()
96     */
97    @Override
98    public String toString()
99     {
100     final var builder = new StringBuilder();
101     builder.append("HistoryOf<>["); //$NON-NLS-1$
102     final int initLength = builder.length();
103     for (final Map.Entry<OffsetDateTime, T> entry : this.history.entrySet())
104      {
105       builder.append(entry.getKey().format(DateTimeFormatter.ISO_DATE_TIME));
106       builder.append('=');
107       builder.append(entry.getValue());
108       builder.append(", "); //$NON-NLS-1$
109      }
110     if (builder.length() > initLength)
111      {
112       builder.setLength(builder.length() - 2);
113      }
114     builder.append(']');
115     return builder.toString();
116    }
117 
118 
119   /**
120    * Is empty.
121    *
122    * @return true: empty; false otherwise
123    */
124   public boolean isEmpty()
125    {
126     return this.history.isEmpty();
127    }
128 
129 
130   /**
131    * Add entry.
132    *
133    * @param since Since datetime
134    * @param entry Entry
135    * @throws NullPointerException If since and/or entry is/are null
136    * @throws IndexOutOfBoundsException If since lies in the future
137    * @throws IllegalArgumentException If entry is already latest in history
138    */
139   public void addEntry(final OffsetDateTime since, final T entry)
140    {
141     Objects.requireNonNull(since, "since"); //$NON-NLS-1$
142     Objects.requireNonNull(entry, "entry"); //$NON-NLS-1$
143     if (since.isAfter(OffsetDateTime.now()))
144      {
145       throw new IndexOutOfBoundsException("since lies in the future!"); //$NON-NLS-1$
146      }
147     if ((!this.history.isEmpty()) && entry.equals(this.getLatestEntry()))
148      {
149       throw new IllegalArgumentException("entry is already latest in HistoryOf!");
150      }
151     this.history.put(since, entry);
152    }
153 
154 
155   /**
156    * Get first entry from history.
157    *
158    * @return First known entry
159    * @throws NoSuchElementException If there is no entry in this HistoryOf
160    */
161   public T getFirstEntry()
162    {
163     return this.history.get(this.history.firstKey());
164    }
165 
166 
167   /**
168    * Get latest entry.
169    *
170    * @return Latest entry
171    * @throws NoSuchElementException If there is no entry in this HistoryOf
172    */
173   public T getLatestEntry()
174    {
175     return this.history.get(this.history.lastKey());
176    }
177 
178 
179   /**
180    * Get entry before latest entry.
181    *
182    * @return Entry before latest entry
183    * @throws NoSuchElementException If there is no entry in this HistoryOf
184    */
185   public T getPreviousEntry()
186    {
187     final Set<OffsetDateTime> keys = this.history.keySet();
188     OffsetDateTime previous = this.history.firstKey();
189     OffsetDateTime latest = this.history.firstKey();
190     for (final OffsetDateTime key : keys)
191      {
192       if (!latest.equals(key))
193        {
194         if (!previous.equals(latest))
195          {
196           previous = latest;
197          }
198         latest = key;
199        }
200      }
201     return this.history.get(previous);
202    }
203 
204 
205   /**
206    * Get history.
207    *
208    * @return History sorted map
209    */
210   public SortedMap<OffsetDateTime, T> getHistory()
211    {
212     return new ConcurrentSkipListMap<>(this.history);
213    }
214 
215  }