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<TimeField, Object></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<V>)</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<TimeField, Object></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