001    /*
002     * Copyleft notice: This is public-domain software and documentation
003     * with no restrictions of any kind.
004     * Please feel free to use any of it in any way you want.
005     * This work is distributed in the hope that it will be useful,
006     * but WITHOUT ANY WARRANTY; without even the implied warranty of
007     * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
008     */
009    
010    
011    package time;
012    
013    import java.util.*;
014    
015    /**
016     * <tt>Time</tt> is a representation of a piece of time. This is the main class of this API.
017     * This class provides a good replacement for <tt>java.util.Date</tt> .
018     *
019     * But its usage is larger than simply a date, as it can represent thinks like:
020     *
021     * <ul>
022     * <li> a normal date. Example: the 20th of October 2005
023     * <li> a Year. Example: 2005
024     * <li> a Month of Year . Example: December 2005
025     * <li> a Week. Example: Week 47 of year 2005
026     * <li> a Hour/Minutes: Example: 13:10
027     * </ul>
028     *
029     * Indeed, <tt>Time</tt> are defined using a <tt>TimeMask</tt> which is the format of the time.
030     *
031     * The <tt>Time</tt> is just a map whose keys are the <tt>TimeField</tt>,
032     * so it implements
033     * <tt>SortedMap&lt;TimeField, Object&gt;</tt>.
034     *
035     * <p>
036     * It is possible to merge several <tt>Time</tt> objects in one. For example, you can
037     * have in one side <tt>Time</tt> objects that represents a day
038     * (like the 20th of October 2005).
039     * In one other side, <tt>Time</tt> objects that represents hour and minutes (for
040     * example 9:00).
041     *
042     * If you want to merge those two <tt>Time</tt> objects to speak about the 20th
043     * of October 2005 9:00, simply use the <a href=#Time(time.Time...)>appropriate constructor</a>.
044     *
045     * <p>
046     * This class is immutable, so it can itself be used as key in <tt>java.util.Map</tt>.
047     * It is also <i>thread-safe</i>.
048     *
049     *
050     * @author Arnaud Roques
051     *
052     * @has - "formatted by" - time.TimeMask
053     * @stereotype "SortedMap"
054     * @stereotype "Comparable"
055     *
056     *
057     */
058    public final class Time implements Comparable<Time>, SortedMap<TimeField, Object>
059    {
060            private final Map<TimeField, Object> values;
061            private final TimeMask mask;
062            
063            /**
064             * Create new <tt>Time</tt> from a map of TimeField.
065             *
066             * @param map   the map whose keys are <tt>TimeField</tt> and values the values
067             *                              of the TimeFields.
068             *
069             */
070            public Time(Map<TimeField, Object> map)
071            {
072                    mask = new TimeMask(map.keySet());
073                    values = Collections.unmodifiableMap(new HashMap<TimeField, Object>(map));
074                    assert(mask.containsAll(values.keySet()));
075            }
076    
077            /**
078             * Create a new <tt>Time</tt> from a <tt>TimeMask</tt> and some values.
079             *
080             * This is the main constructor for <tt>Time</tt>.
081             * The order of <tt>values</tt> should be the same as the order in the <tt>mask</tt>.
082             *
083             * @param       mask    the <tt>TimeMask</tt> which defines the format
084             * @param       values          all values of the TimeFields contained in the <tt>TimeMask</tt>.
085             *
086             * @throws IllegalArgumentException if <tt>mask</tt> and <tt>values</tt> do not have the
087             *                      same size.
088             * @throws IllegalArgumentException if a value is incompatible with the <tt>mask</tt>.
089             *
090             */
091            public Time(TimeMask mask, Object... values)
092            {
093                    this.mask = mask;
094                    
095                    if (this.mask.size()!=values.length) throw new IllegalArgumentException("Bad mask length");
096                    
097                    Map<TimeField, Object> tmp = new HashMap<TimeField, Object>(values.length);
098                    
099                    int i = 0;
100                    for (TimeField f : mask.getFields())
101                    {
102                            if (f.isValueOk(values[i])==false)
103                                    throw new IllegalArgumentException(f+" "+values[i]);
104                            tmp.put(f, values[i]);  
105                            i++;
106                    }
107                    this.values = tmp;
108                    assert(mask.containsAll(this.values.keySet()));
109            }
110            
111            /**
112             * Create a new <tt>Time</tt> object by merging existing other <tt>Time</tt> objects.
113             *
114             * The new <tt>Time</tt> objects contains all keys and values of all other <tt>Time</tt>
115             * objects.
116             *
117             * @throws IllegalArgumentException     if two or more <tt>Time</tt> objects has the same
118             *              <tt>TimeField</tt> with different values.
119             *
120             */
121            public Time(Time... otherTimes)
122            {
123                    Collection<TimeField> fields = new ArrayList<TimeField>();
124                    mask = new TimeMask(fields);
125                    values = new HashMap<TimeField, Object>();
126                    for (Time t : otherTimes)
127                    {
128                            fields.addAll(t.getTimeMask());
129                            values.putAll(t);
130                    }
131                    
132                    for (Time t : otherTimes)
133                    {
134                            if (values.entrySet().containsAll(t.entrySet())==false)
135                                    throw new IllegalArgumentException();
136                    }
137            
138            }
139            
140            /*
141             * Create a new <tt>Time</tt> from a <tt>TimeMask</tt> and another <tt>Time</tt>.
142             *
143             * This is the main constructor for <tt>Time</tt>.
144             * The order of <tt>values</tt> should be the same as the order in the <tt>mask</tt>.
145             *
146             * @param       mask    the <tt>TimeMask</tt> defining the format
147             * @param       values          all values of the TimeFields contained in the <tt>TimeMask</tt>.
148             *
149             * @throws IllegalArgumentException if <tt>mask</tt> and <tt>values</tt> dont have the
150             *                      same size.
151             * @throws IllegalArgumentException if a values is incompatible with the <tt>mask</tt>.
152             *
153             *
154            public Time(TimeMask mask, Time other) throws IllegalArgumentException
155            {
156                    this.mask = mask;
157                    Map<TimeField, Comparable> tmp = new HashMap<TimeField, Comparable>(mask.size());
158                    
159                    for (TimeField f : mask.getFields())
160                    {
161                            Comparable v = other.get(f);
162                            if (v==null)
163                                    throw new IllegalArgumentException(""+f);
164                            tmp.put(f, v);
165                    }
166                    this.values = Collections.unmodifiableMap(tmp);
167            }*/
168            
169            /**
170             * Return the format of this <tt>Time</tt> object.
171             *
172             * @return      the <tt>TimeMask</tt> of this <tt>Time</tt> object.
173             */
174            public TimeMask getTimeMask()
175            {
176                    return mask;
177            }
178            
179            /**
180             * Return the value of a <tt>TimeField</tt> of this <tt>Time</tt>.
181             *
182             * <p>
183             * You should consider the <tt>get(TimeField&lt;V&gt;)</tt> methods
184             * instead, as it avoid casting.
185             *
186             * @return the value of the <tt>TimeField</tt> provided.
187             *
188             * @see #get(TimeField)
189             * @throws NullPointerException if <tt>field</tt> is <tt>null</tt>.
190             * @throws ClassCastException if <tt>field</tt> is not a <tt>TimeField</tt>.
191             * @throws IllegalArgumentException if the <tt>field</tt> is not in this <tt>Time</tt>
192             * object keys'.
193             *
194             * @hidden
195             */ 
196            public Object get(Object field)
197            {
198                    if (field==null) throw new NullPointerException();
199                    if (field instanceof TimeField==false) throw new ClassCastException();
200                    Object v = values.get(field);
201                    if (v==null) throw new IllegalArgumentException();
202                    return v;
203            }
204            
205            /**
206             * Return the value of a <tt>TimeField</tt> of this <tt>Time</tt>.
207             *
208             * @return the value of the <tt>TimeField</tt> provided.
209             *
210             * @throws NullPointerException if <tt>field</tt> is <tt>null</tt>.
211             * @throws IllegalArgumentException if the <tt>field</tt> is not in this <tt>Time</tt>
212             * object keys'.
213             */ 
214            public <V extends Object> V get(TimeField<V> field)
215            {
216                    if (field==null) throw new NullPointerException();
217                    V v = (V) values.get(field);
218                    if (v==null) throw new IllegalArgumentException();
219                    return v;
220            }
221            
222            
223            /*
224            public Time removeField(TimeField... f)
225            {
226                    TimeMask newMask = mask.remove(f);
227                    Map<TimeField, Comparable> newValues = new HashMap<TimeField, Comparable>(values);
228                    newValues.keySet().removeAll(Arrays.asList(f));
229                    return new Time(newValues);
230            }*/
231            
232            /**
233             * Create a new <tt>Time</tt> by updating or adding a new <tt>TimeField</tt> in this
234             * <tt>Time</tt> object.
235             *
236             * @return      a new <tt>Time</tt> that contains all <tt>TimeField</tt> of this
237             *                      <tt>Time</tt> object and the <tt>field</tt> provided.
238             *
239             * @throws IllegalArgumentException     if <tt>value</tt> is not a valid value for <tt>field</tt>
240             */
241            public Time update(TimeField field, Object value)
242            {
243                    if (field.isValueOk(value)==false)
244                            throw new IllegalArgumentException(field+" "+value);
245                    Map<TimeField, Object> newValues = new HashMap<TimeField, Object>(values);
246                    newValues.put(field, value);
247                    return new Time(newValues);
248                    
249            }
250            
251            
252            /**
253             * Compares this <tt>Time</tt> with the specified <tt>Time</tt> for order.
254             *
255             * The comparaison is made with the <i>most significant</i> TimeFields of each
256             * <tt>Time</tt>.
257             *
258             * If values of both the <i>most significant</i> TimeField of each <tt>Time</tt>
259             * are the same, the comparaison is made on the next <i>most significant</i> TimeField,
260             * until a difference is founded.
261             *
262             * If all TimeFields of each <tt>Time</tt> object have the same values, a zero is
263             * return (and this is consistent with equals).
264             *
265             * @return a negative integer, zero, or a positive integer as this <tt>Time</tt>
266             * is less than, equal to, or greater than the specified <tt>Time</tt>.
267             *
268             * @throws IllegalArgumentException     if both <tt>Time</tt> objects do not have the same
269             *                      <tt>TimeMask</tt>.
270             */
271            public int compareTo(Time other)
272            {
273                    if (mask.equals(other.mask)==false) throw new IllegalArgumentException();
274                    
275                    for (TimeField f : mask.getFields())
276                    {
277                            Object vThis = values.get(f);
278                            Object vOther = other.values.get(f);
279                            //int r = vThis.compareTo(vOther);
280                            int r = f.compare(vThis, vOther);
281                            if (r!=0) return r;
282                    }
283                    assert(this.equals(other));
284                    return 0;
285            }
286            
287            /**
288             * Compare two <tt>Time</tt> for equality.
289             * The result is <tt>true</tt> if and only if the argument is not <tt>null</tt>
290             * and is a <tt>Time</tt> object that represents the same time as this object.
291             *
292             * @return <tt>true</tt> if the objects are the same; false otherwise.
293             * @hidden
294             */
295            @Override
296            public boolean equals(Object o)
297            {
298                    if (!(o instanceof Time)) return false;
299                    Time other = (Time) o;
300                    return values.equals(other.values);
301            }
302            
303            
304            /**
305             * Convert this <tt>Time</tt> object in a string.
306             *
307             * @return a <tt>String</tt> describing this object.
308             * @hidden
309             */
310            @Override
311            public String toString()
312            {
313                    TreeMap<TimeField, Object> treeValues = new TreeMap<TimeField, Object>(values);
314                    return treeValues.toString();
315            }
316            
317            /**
318             * Test if this <tt>Time</tt> object uses a certain <tt>TimeMask</tt>.
319             *
320             * @param mask          the <tt>TimeMask</tt> to be tested
321             * @return      <tt>true</tt> if <tt>mask</tt> is the <tt>TimeMask</tt> of
322             *      this <tt>Time</tt> object
323             */
324            public boolean sameTimeMask(TimeMask mask)
325            {
326                    return this.mask.equals(mask);
327            }
328    
329            /**
330             * Test if two <tt>Time</tt> objects use the same <tt>TimeMask</tt>.
331             *
332             * @param other         the <tt>Time</tt> to test with
333             * @return      <tt>true</tt> if this <tt>Time</tt> object has the same <tt>TimeMask</tt>
334             *      than <tt>other</tt>.
335             */
336            public boolean sameTimeMask(Time other)
337            {
338                    return this.mask.equals(other.mask);
339            }
340            
341            /**
342             * Returns a set view of the mappings contained in this <tt>Time</tt> object.
343             *
344             * The set's iterator returns the mappings in ascending key order.
345             * Each element in the returned set is a <tt>Map.Entry&lt;TimeField, Object&gt;</tt>.
346             *
347             *
348             * <p>This method is can be useful to test in an instant is part of another instant.
349             * Example:
350             * <br><pre>
351             *      Time myMonth = new Time(MONTH_MASK, 2005, Month.DECEMBER);
352             *      Time other = new Time(...);
353             *      boolean test = other.entrySet().containsAll(myMonth.entrySet());
354             * </pre><br>
355             * @return a set view of the mappings contained in this <tt>Time</tt> object.
356             * @hidden
357             */
358            public Set<Map.Entry<TimeField, Object>> entrySet()
359            {
360                    return values.entrySet();
361            }
362            
363            /**
364             * Returns a collection view of the values contained in this <tt>Time</tt> object.
365             *
366             * <p>
367             * The collection's iterator will return the values in the order
368             * that their corresponding keys appear in this <tt>Time</tt> object. 
369             *
370             * @return a collection view of the values contained in this <tt>Time</tt> object.
371             * @hidden
372             */
373            public Collection<Object> values()
374            {
375                    return values.values();
376            }
377            
378            /**
379             * Returns a Set view of the keys contained in this <tt>Time</tt> object.
380             *
381             * The set's iterator will return the keys in ascending order.
382             *
383             * @return a set view of the keys contained in this <tt>Time</tt> object.
384             * @hidden
385             */
386            public Set<TimeField> keySet()
387            {
388                    return values.keySet();
389            }
390            
391            /*public Comparable get(Object key)
392            {
393                    return values.get(key);
394            }*/
395            
396            /**
397             * Returns <tt>true</tt> if this <tt>Time</tt> object maps one or more keys to the specified value.
398             *
399             * @return <tt>true</tt> if this <tt>Time</tt> object maps one or more keys to the specified value.
400             *
401             * @throws NullPointerException if <tt>value</tt> is <tt>null</tt>.
402             * @hidden
403             */
404            public boolean containsValue(Object value)
405            {
406                    if (value==null) throw new NullPointerException();
407                    
408                    return values.containsValue(value);
409            }
410            
411            /**
412             * Returns <tt>true</tt> if this <tt>Time</tt> object contains a mapping for the specified key.
413             *
414             * @return <tt>true</tt> if this <tt>Time</tt> object contains a mapping for the specified key.
415             *
416             * @throws NullPointerException if <tt>value</tt> is <tt>null</tt>.
417             * @throws ClassCastException   if <tt>value</tt> is not a <tt>TimeField</tt>.
418             * @hidden
419             */
420            public boolean containsKey(Object key)
421            {
422                    if (key==null) throw new NullPointerException();
423                    if (key instanceof TimeField==false) throw new ClassCastException();
424                    
425                    return values.containsKey(key);
426            }
427            
428            /**
429             * Returns <tt>true</tt> if this <tt>Time</tt> object contains no key-value mappings.
430             *
431             * @return <tt>true</tt> if this <tt>Time</tt> object contains no key-value mappings.
432             * @hidden
433             */
434            public boolean isEmpty()
435            {
436                    return values.isEmpty();
437            }
438            
439            /**
440             * Return the number of <tt>TimeField</tt> in this <tt>Time</tt> object.
441             *
442             *
443             * @return the number of <tt>TimeField</tt> in this <tt>Time</tt> object.
444             * @hidden
445             */
446            public int size()
447            {
448                    return values.size();
449            }
450            
451            /**
452             * As <tt>TimeField</tt> are immutable, throws a <tt>UnsupportedOperationException</tt>.
453             * @hidden
454             */
455            public void clear()
456            {
457                    throw new UnsupportedOperationException();
458            }
459            
460            /**
461             * As <tt>TimeField</tt> are immutable, throws a <tt>UnsupportedOperationException</tt>.
462             * @hidden
463             */
464            public void putAll(Map<? extends TimeField,? extends Object> t)
465            {
466                    throw new UnsupportedOperationException();
467            }
468            
469            /**
470             * As <tt>TimeField</tt> are immutable, throws a <tt>UnsupportedOperationException</tt>.
471             * @hidden
472             */
473            public Comparable remove(Object key)
474            {
475                    throw new UnsupportedOperationException();
476            }
477            
478            /**
479             * As <tt>TimeField</tt> are immutable, throws a <tt>UnsupportedOperationException</tt>.
480             * @hidden
481             */
482             public Comparable put(TimeField key, Object value)
483             {
484                     throw new UnsupportedOperationException();
485             }
486             
487             
488             /**
489              * Return the less significant <tt>TimeField</tt> in this <tt>Time</tt> object.
490              *
491              * @return the less significant <tt>TimeField</tt> in this <tt>Time</tt> object.
492              * @hidden
493              */
494             public TimeField lastKey()
495             {
496                    return mask.last();
497             }
498             
499    
500             /**
501              * Return the most significant <tt>TimeField</tt> in this <tt>Time</tt> object.
502              *
503              * @return the most significant <tt>TimeField</tt> in this <tt>Time</tt> object.
504              * @hidden
505              */
506             public TimeField firstKey()
507             {
508                    return mask.first();
509             }
510             
511             /**
512              * As the <i>naturel order</i> if <tt>TimeField</tt> is used, returns <tt>null</tt>.
513              * <tt>TimeField</tt> are indeed sorted in this <tt>Time</tt> object.
514              *
515              * @return <tt>null</tt>.
516              * @hidden
517              */
518             public Comparator<? super TimeField> comparator()
519             {
520                    return null;
521             }
522             
523             /**
524              * Return a new <tt>Time</tt> object that contains all <tt>TimeField</tt> less
525              * significant than <tt>fromKey</tt> (including).
526              *
527              * @return a new <tt>Time</tt> object that contains all <tt>TimeField</tt> less
528              * significant than <tt>fromKey</tt> (including).
529              * @hidden
530              */
531             public Time tailMap(TimeField fromKey)
532             {
533                    TreeMap<TimeField, Object> tmp = new TreeMap<TimeField, Object>(values);
534                    return new Time(tmp.tailMap(fromKey));
535             }
536    
537             /**
538              * Return a new <tt>Time</tt> object that contains all <tt>TimeField</tt> more
539              * significant than <tt>toKey</tt> (excluding).
540              *
541              * @return a new <tt>Time</tt> object that contains all <tt>TimeField</tt> more
542              * significant than <tt>toKey</tt> (excluding).
543              * @hidden
544              */
545             public Time headMap(TimeField toKey)
546             {
547                     TreeMap<TimeField, Object> tmp = new TreeMap<TimeField, Object>(values);
548                     return new Time(tmp.headMap(toKey));
549             }
550             
551             /**
552              * Return a new <tt>Time</tt> object that contains all <tt>TimeField</tt> less
553              * significant than <tt>fromKey</tt> (including) and more
554              * significant than <tt>toKey</tt> (excluding).
555              *
556              * @return a new <tt>Time</tt> object that contains all <tt>TimeField</tt> less
557              * significant than <tt>fromKey</tt> (including) and more
558              * significant than <tt>toKey</tt> (excluding).
559              * @hidden
560              */
561             public Time subMap(TimeField fromKey, TimeField toKey)
562             {
563                     TreeMap<TimeField, Object> tmp = new TreeMap<TimeField, Object>(values);
564                     return new Time(tmp.subMap(fromKey, toKey));
565             }
566             
567             
568             /**
569              * Return a new <tt>Time</tt> object that contains only some <tt>TimeField</tt>
570              * of this <tt>Time</tt> object.
571              *
572              * @param fields       the <tt>TimeField</tt> to be kept.
573              *
574              * @return a new <tt>Time</tt> object that contains only some <tt>TimeField</tt>
575              * @hidden
576              */
577             public Time subMap(Collection<TimeField> fields)
578             {
579                Map<TimeField, Object> valuesMap = new HashMap<TimeField, Object>(values);
580                    valuesMap.keySet().removeAll(fields);
581                    return new Time(valuesMap);
582             }
583    }
584