001 /*
002 * Copyright 2001-2005 Stephen Colebourne
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 * http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016 package org.joda.time;
017
018 import java.io.Serializable;
019 import java.util.ArrayList;
020 import java.util.Arrays;
021 import java.util.HashMap;
022 import java.util.List;
023 import java.util.Map;
024
025 import org.joda.time.field.FieldUtils;
026
027 /**
028 * Controls a period implementation by specifying which duration fields are to be used.
029 * <p>
030 * The following implementations are provided:
031 * <ul>
032 * <li>Standard - years, months, weeks, days, hours, minutes, seconds, millis
033 * <li>YearMonthDayTime - years, months, days, hours, minutes, seconds, millis
034 * <li>YearMonthDay - years, months, days
035 * <li>YearWeekDayTime - years, weeks, days, hours, minutes, seconds, millis
036 * <li>YearWeekDay - years, weeks, days
037 * <li>YearDayTime - years, days, hours, minutes, seconds, millis
038 * <li>YearDay - years, days, hours
039 * <li>DayTime - days, hours, minutes, seconds, millis
040 * <li>Time - hours, minutes, seconds, millis
041 * <li>plus one for each single type
042 * </ul>
043 *
044 * <p>
045 * PeriodType is thread-safe and immutable, and all subclasses must be as well.
046 *
047 * @author Brian S O'Neill
048 * @author Stephen Colebourne
049 * @since 1.0
050 */
051 public class PeriodType implements Serializable {
052 /** Serialization version */
053 private static final long serialVersionUID = 2274324892792009998L;
054
055 /** Cache of all the known types. */
056 private static final Map cTypes = new HashMap(32);
057
058 static int YEAR_INDEX = 0;
059 static int MONTH_INDEX = 1;
060 static int WEEK_INDEX = 2;
061 static int DAY_INDEX = 3;
062 static int HOUR_INDEX = 4;
063 static int MINUTE_INDEX = 5;
064 static int SECOND_INDEX = 6;
065 static int MILLI_INDEX = 7;
066
067 private static PeriodType cStandard;
068 private static PeriodType cYMDTime;
069 private static PeriodType cYMD;
070 private static PeriodType cYWDTime;
071 private static PeriodType cYWD;
072 private static PeriodType cYDTime;
073 private static PeriodType cYD;
074 private static PeriodType cDTime;
075 private static PeriodType cTime;
076
077 private static PeriodType cYears;
078 private static PeriodType cMonths;
079 private static PeriodType cWeeks;
080 private static PeriodType cDays;
081 private static PeriodType cHours;
082 private static PeriodType cMinutes;
083 private static PeriodType cSeconds;
084 private static PeriodType cMillis;
085
086 /**
087 * Gets a type that defines all standard fields.
088 * <ul>
089 * <li>years
090 * <li>months
091 * <li>weeks
092 * <li>days
093 * <li>hours
094 * <li>minutes
095 * <li>seconds
096 * <li>milliseconds
097 * </ul>
098 *
099 * @return the period type
100 */
101 public static PeriodType standard() {
102 PeriodType type = cStandard;
103 if (type == null) {
104 type = new PeriodType(
105 "Standard",
106 new DurationFieldType[] {
107 DurationFieldType.years(), DurationFieldType.months(),
108 DurationFieldType.weeks(), DurationFieldType.days(),
109 DurationFieldType.hours(), DurationFieldType.minutes(),
110 DurationFieldType.seconds(), DurationFieldType.millis(),
111 },
112 new int[] { 0, 1, 2, 3, 4, 5, 6, 7, }
113 );
114 cStandard = type;
115 }
116 return type;
117 }
118
119 /**
120 * Gets a type that defines all standard fields except weeks.
121 * <ul>
122 * <li>years
123 * <li>months
124 * <li>days
125 * <li>hours
126 * <li>minutes
127 * <li>seconds
128 * <li>milliseconds
129 * </ul>
130 *
131 * @return the period type
132 */
133 public static PeriodType yearMonthDayTime() {
134 PeriodType type = cYMDTime;
135 if (type == null) {
136 type = new PeriodType(
137 "YearMonthDayTime",
138 new DurationFieldType[] {
139 DurationFieldType.years(), DurationFieldType.months(),
140 DurationFieldType.days(),
141 DurationFieldType.hours(), DurationFieldType.minutes(),
142 DurationFieldType.seconds(), DurationFieldType.millis(),
143 },
144 new int[] { 0, 1, -1, 2, 3, 4, 5, 6, }
145 );
146 cYMDTime = type;
147 }
148 return type;
149 }
150
151 /**
152 * Gets a type that defines the year, month and day fields.
153 * <ul>
154 * <li>years
155 * <li>months
156 * <li>days
157 * </ul>
158 *
159 * @return the period type
160 * @since 1.1
161 */
162 public static PeriodType yearMonthDay() {
163 PeriodType type = cYMD;
164 if (type == null) {
165 type = new PeriodType(
166 "YearMonthDay",
167 new DurationFieldType[] {
168 DurationFieldType.years(), DurationFieldType.months(),
169 DurationFieldType.days(),
170 },
171 new int[] { 0, 1, -1, 2, -1, -1, -1, -1, }
172 );
173 cYMD = type;
174 }
175 return type;
176 }
177
178 /**
179 * Gets a type that defines all standard fields except months.
180 * <ul>
181 * <li>years
182 * <li>weeks
183 * <li>days
184 * <li>hours
185 * <li>minutes
186 * <li>seconds
187 * <li>milliseconds
188 * </ul>
189 *
190 * @return the period type
191 */
192 public static PeriodType yearWeekDayTime() {
193 PeriodType type = cYWDTime;
194 if (type == null) {
195 type = new PeriodType(
196 "YearWeekDayTime",
197 new DurationFieldType[] {
198 DurationFieldType.years(),
199 DurationFieldType.weeks(), DurationFieldType.days(),
200 DurationFieldType.hours(), DurationFieldType.minutes(),
201 DurationFieldType.seconds(), DurationFieldType.millis(),
202 },
203 new int[] { 0, -1, 1, 2, 3, 4, 5, 6, }
204 );
205 cYWDTime = type;
206 }
207 return type;
208 }
209
210 /**
211 * Gets a type that defines year, week and day fields.
212 * <ul>
213 * <li>years
214 * <li>weeks
215 * <li>days
216 * </ul>
217 *
218 * @return the period type
219 * @since 1.1
220 */
221 public static PeriodType yearWeekDay() {
222 PeriodType type = cYWD;
223 if (type == null) {
224 type = new PeriodType(
225 "YearWeekDay",
226 new DurationFieldType[] {
227 DurationFieldType.years(),
228 DurationFieldType.weeks(), DurationFieldType.days(),
229 },
230 new int[] { 0, -1, 1, 2, -1, -1, -1, -1, }
231 );
232 cYWD = type;
233 }
234 return type;
235 }
236
237 /**
238 * Gets a type that defines all standard fields except months and weeks.
239 * <ul>
240 * <li>years
241 * <li>days
242 * <li>hours
243 * <li>minutes
244 * <li>seconds
245 * <li>milliseconds
246 * </ul>
247 *
248 * @return the period type
249 */
250 public static PeriodType yearDayTime() {
251 PeriodType type = cYDTime;
252 if (type == null) {
253 type = new PeriodType(
254 "YearDayTime",
255 new DurationFieldType[] {
256 DurationFieldType.years(), DurationFieldType.days(),
257 DurationFieldType.hours(), DurationFieldType.minutes(),
258 DurationFieldType.seconds(), DurationFieldType.millis(),
259 },
260 new int[] { 0, -1, -1, 1, 2, 3, 4, 5, }
261 );
262 cYDTime = type;
263 }
264 return type;
265 }
266
267 /**
268 * Gets a type that defines the year and day fields.
269 * <ul>
270 * <li>years
271 * <li>days
272 * </ul>
273 *
274 * @return the period type
275 * @since 1.1
276 */
277 public static PeriodType yearDay() {
278 PeriodType type = cYD;
279 if (type == null) {
280 type = new PeriodType(
281 "YearDay",
282 new DurationFieldType[] {
283 DurationFieldType.years(), DurationFieldType.days(),
284 },
285 new int[] { 0, -1, -1, 1, -1, -1, -1, -1, }
286 );
287 cYD = type;
288 }
289 return type;
290 }
291
292 /**
293 * Gets a type that defines all standard fields from days downwards.
294 * <ul>
295 * <li>days
296 * <li>hours
297 * <li>minutes
298 * <li>seconds
299 * <li>milliseconds
300 * </ul>
301 *
302 * @return the period type
303 */
304 public static PeriodType dayTime() {
305 PeriodType type = cDTime;
306 if (type == null) {
307 type = new PeriodType(
308 "DayTime",
309 new DurationFieldType[] {
310 DurationFieldType.days(),
311 DurationFieldType.hours(), DurationFieldType.minutes(),
312 DurationFieldType.seconds(), DurationFieldType.millis(),
313 },
314 new int[] { -1, -1, -1, 0, 1, 2, 3, 4, }
315 );
316 cDTime = type;
317 }
318 return type;
319 }
320
321 /**
322 * Gets a type that defines all standard time fields.
323 * <ul>
324 * <li>hours
325 * <li>minutes
326 * <li>seconds
327 * <li>milliseconds
328 * </ul>
329 *
330 * @return the period type
331 */
332 public static PeriodType time() {
333 PeriodType type = cTime;
334 if (type == null) {
335 type = new PeriodType(
336 "Time",
337 new DurationFieldType[] {
338 DurationFieldType.hours(), DurationFieldType.minutes(),
339 DurationFieldType.seconds(), DurationFieldType.millis(),
340 },
341 new int[] { -1, -1, -1, -1, 0, 1, 2, 3, }
342 );
343 cTime = type;
344 }
345 return type;
346 }
347
348 /**
349 * Gets a type that defines just the years field.
350 *
351 * @return the period type
352 */
353 public static PeriodType years() {
354 PeriodType type = cYears;
355 if (type == null) {
356 type = new PeriodType(
357 "Years",
358 new DurationFieldType[] { DurationFieldType.years() },
359 new int[] { 0, -1, -1, -1, -1, -1, -1, -1, }
360 );
361 cYears = type;
362 }
363 return type;
364 }
365
366 /**
367 * Gets a type that defines just the months field.
368 *
369 * @return the period type
370 */
371 public static PeriodType months() {
372 PeriodType type = cMonths;
373 if (type == null) {
374 type = new PeriodType(
375 "Months",
376 new DurationFieldType[] { DurationFieldType.months() },
377 new int[] { -1, 0, -1, -1, -1, -1, -1, -1, }
378 );
379 cMonths = type;
380 }
381 return type;
382 }
383
384 /**
385 * Gets a type that defines just the weeks field.
386 *
387 * @return the period type
388 */
389 public static PeriodType weeks() {
390 PeriodType type = cWeeks;
391 if (type == null) {
392 type = new PeriodType(
393 "Weeks",
394 new DurationFieldType[] { DurationFieldType.weeks() },
395 new int[] { -1, -1, 0, -1, -1, -1, -1, -1, }
396 );
397 cWeeks = type;
398 }
399 return type;
400 }
401
402 /**
403 * Gets a type that defines just the days field.
404 *
405 * @return the period type
406 */
407 public static PeriodType days() {
408 PeriodType type = cDays;
409 if (type == null) {
410 type = new PeriodType(
411 "Days",
412 new DurationFieldType[] { DurationFieldType.days() },
413 new int[] { -1, -1, -1, 0, -1, -1, -1, -1, }
414 );
415 cDays = type;
416 }
417 return type;
418 }
419
420 /**
421 * Gets a type that defines just the hours field.
422 *
423 * @return the period type
424 */
425 public static PeriodType hours() {
426 PeriodType type = cHours;
427 if (type == null) {
428 type = new PeriodType(
429 "Hours",
430 new DurationFieldType[] { DurationFieldType.hours() },
431 new int[] { -1, -1, -1, -1, 0, -1, -1, -1, }
432 );
433 cHours = type;
434 }
435 return type;
436 }
437
438 /**
439 * Gets a type that defines just the minutes field.
440 *
441 * @return the period type
442 */
443 public static PeriodType minutes() {
444 PeriodType type = cMinutes;
445 if (type == null) {
446 type = new PeriodType(
447 "Minutes",
448 new DurationFieldType[] { DurationFieldType.minutes() },
449 new int[] { -1, -1, -1, -1, -1, 0, -1, -1, }
450 );
451 cMinutes = type;
452 }
453 return type;
454 }
455
456 /**
457 * Gets a type that defines just the seconds field.
458 *
459 * @return the period type
460 */
461 public static PeriodType seconds() {
462 PeriodType type = cSeconds;
463 if (type == null) {
464 type = new PeriodType(
465 "Seconds",
466 new DurationFieldType[] { DurationFieldType.seconds() },
467 new int[] { -1, -1, -1, -1, -1, -1, 0, -1, }
468 );
469 cSeconds = type;
470 }
471 return type;
472 }
473
474 /**
475 * Gets a type that defines just the millis field.
476 *
477 * @return the period type
478 */
479 public static PeriodType millis() {
480 PeriodType type = cMillis;
481 if (type == null) {
482 type = new PeriodType(
483 "Millis",
484 new DurationFieldType[] { DurationFieldType.millis() },
485 new int[] { -1, -1, -1, -1, -1, -1, -1, 0, }
486 );
487 cMillis = type;
488 }
489 return type;
490 }
491
492 /**
493 * Gets a period type that contains the duration types of the array.
494 * <p>
495 * Only the 8 standard duration field types are supported.
496 *
497 * @param types the types to include in the array.
498 * @return the period type
499 * @since 1.1
500 */
501 public static synchronized PeriodType forFields(DurationFieldType[] types) {
502 if (types == null || types.length == 0) {
503 throw new IllegalArgumentException("Types array must not be null or empty");
504 }
505 for (int i = 0; i < types.length; i++) {
506 if (types[i] == null) {
507 throw new IllegalArgumentException("Types array must not contain null");
508 }
509 }
510 Map cache = cTypes;
511 if (cTypes.isEmpty()) {
512 cache.put(standard(), standard());
513 cache.put(yearMonthDayTime(), yearMonthDayTime());
514 cache.put(yearMonthDay(), yearMonthDay());
515 cache.put(yearWeekDayTime(), yearWeekDayTime());
516 cache.put(yearWeekDay(), yearWeekDay());
517 cache.put(yearDayTime(), yearDayTime());
518 cache.put(yearDay(), yearDay());
519 cache.put(dayTime(), dayTime());
520 cache.put(time(), time());
521 cache.put(years(), years());
522 cache.put(months(), months());
523 cache.put(weeks(), weeks());
524 cache.put(days(), days());
525 cache.put(hours(), hours());
526 cache.put(minutes(), minutes());
527 cache.put(seconds(), seconds());
528 cache.put(millis(), millis());
529 }
530 PeriodType inPartType = new PeriodType(null, types, null);
531 Object cached = cache.get(inPartType);
532 if (cached instanceof PeriodType) {
533 return (PeriodType) cached;
534 }
535 if (cached != null) {
536 throw new IllegalArgumentException("PeriodType does not support fields: " + cached);
537 }
538 PeriodType type = standard();
539 List list = new ArrayList(Arrays.asList(types));
540 if (list.remove(DurationFieldType.years()) == false) {
541 type = type.withYearsRemoved();
542 }
543 if (list.remove(DurationFieldType.months()) == false) {
544 type = type.withMonthsRemoved();
545 }
546 if (list.remove(DurationFieldType.weeks()) == false) {
547 type = type.withWeeksRemoved();
548 }
549 if (list.remove(DurationFieldType.days()) == false) {
550 type = type.withDaysRemoved();
551 }
552 if (list.remove(DurationFieldType.hours()) == false) {
553 type = type.withHoursRemoved();
554 }
555 if (list.remove(DurationFieldType.minutes()) == false) {
556 type = type.withMinutesRemoved();
557 }
558 if (list.remove(DurationFieldType.seconds()) == false) {
559 type = type.withSecondsRemoved();
560 }
561 if (list.remove(DurationFieldType.millis()) == false) {
562 type = type.withMillisRemoved();
563 }
564 if (list.size() > 0) {
565 cache.put(inPartType, list);
566 throw new IllegalArgumentException("PeriodType does not support fields: " + list);
567 }
568 // recheck cache in case initial array order was wrong
569 PeriodType checkPartType = new PeriodType(null, type.iTypes, null);
570 PeriodType checkedType = (PeriodType) cache.get(checkPartType);
571 if (checkedType != null) {
572 cache.put(inPartType, checkedType);
573 return checkedType;
574 }
575 cache.put(inPartType, type);
576 return type;
577 }
578
579 //-----------------------------------------------------------------------
580 /** The name of the type */
581 private final String iName;
582 /** The array of types */
583 private final DurationFieldType[] iTypes;
584 /** The array of indices */
585 private final int[] iIndices;
586
587 /**
588 * Constructor.
589 *
590 * @param name the name
591 * @param types the types
592 * @param indices the indices
593 */
594 protected PeriodType(String name, DurationFieldType[] types, int[] indices) {
595 super();
596 iName = name;
597 iTypes = types;
598 iIndices = indices;
599 }
600
601 //-----------------------------------------------------------------------
602 /**
603 * Gets the name of the period type.
604 *
605 * @return the name
606 */
607 public String getName() {
608 return iName;
609 }
610
611 /**
612 * Gets the number of fields in the period type.
613 *
614 * @return the number of fields
615 */
616 public int size() {
617 return iTypes.length;
618 }
619
620 /**
621 * Gets the field type by index.
622 *
623 * @param index the index to retrieve
624 * @return the field type
625 * @throws IndexOutOfBoundsException if the index is invalid
626 */
627 public DurationFieldType getFieldType(int index) {
628 return iTypes[index];
629 }
630
631 /**
632 * Checks whether the field specified is supported by this period.
633 *
634 * @param type the type to check, may be null which returns false
635 * @return true if the field is supported
636 */
637 public boolean isSupported(DurationFieldType type) {
638 return (indexOf(type) >= 0);
639 }
640
641 /**
642 * Gets the index of the field in this period.
643 *
644 * @param type the type to check, may be null which returns -1
645 * @return the index of -1 if not supported
646 */
647 public int indexOf(DurationFieldType type) {
648 for (int i = 0, isize = size(); i < isize; i++) {
649 if (iTypes[i] == type) {
650 return i;
651 }
652 }
653 return -1;
654 }
655
656 /**
657 * Gets a debugging to string.
658 *
659 * @return a string
660 */
661 public String toString() {
662 return "PeriodType[" + getName() + "]";
663 }
664
665 //-----------------------------------------------------------------------
666 /**
667 * Gets the indexed field part of the period.
668 *
669 * @param period the period to query
670 * @param index the index to use
671 * @return the value of the field, zero if unsupported
672 */
673 int getIndexedField(ReadablePeriod period, int index) {
674 int realIndex = iIndices[index];
675 return (realIndex == -1 ? 0 : period.getValue(realIndex));
676 }
677
678 /**
679 * Sets the indexed field part of the period.
680 *
681 * @param period the period to query
682 * @param index the index to use
683 * @param values the array to populate
684 * @param newValue the value to set
685 * @throws UnsupportedOperationException if not supported
686 */
687 boolean setIndexedField(ReadablePeriod period, int index, int[] values, int newValue) {
688 int realIndex = iIndices[index];
689 if (realIndex == -1) {
690 throw new UnsupportedOperationException("Field is not supported");
691 }
692 values[realIndex] = newValue;
693 return true;
694 }
695
696 /**
697 * Adds to the indexed field part of the period.
698 *
699 * @param period the period to query
700 * @param index the index to use
701 * @param values the array to populate
702 * @param valueToAdd the value to add
703 * @return true if the array is updated
704 * @throws UnsupportedOperationException if not supported
705 */
706 boolean addIndexedField(ReadablePeriod period, int index, int[] values, int valueToAdd) {
707 if (valueToAdd == 0) {
708 return false;
709 }
710 int realIndex = iIndices[index];
711 if (realIndex == -1) {
712 throw new UnsupportedOperationException("Field is not supported");
713 }
714 values[realIndex] = FieldUtils.safeAdd(values[realIndex], valueToAdd);
715 return true;
716 }
717
718 //-----------------------------------------------------------------------
719 /**
720 * Returns a version of this PeriodType instance that does not support years.
721 *
722 * @return a new period type that supports the original set of fields except years
723 */
724 public PeriodType withYearsRemoved() {
725 return withFieldRemoved(0, "NoYears");
726 }
727
728 /**
729 * Returns a version of this PeriodType instance that does not support months.
730 *
731 * @return a new period type that supports the original set of fields except months
732 */
733 public PeriodType withMonthsRemoved() {
734 return withFieldRemoved(1, "NoMonths");
735 }
736
737 /**
738 * Returns a version of this PeriodType instance that does not support weeks.
739 *
740 * @return a new period type that supports the original set of fields except weeks
741 */
742 public PeriodType withWeeksRemoved() {
743 return withFieldRemoved(2, "NoWeeks");
744 }
745
746 /**
747 * Returns a version of this PeriodType instance that does not support days.
748 *
749 * @return a new period type that supports the original set of fields except days
750 */
751 public PeriodType withDaysRemoved() {
752 return withFieldRemoved(3, "NoDays");
753 }
754
755 /**
756 * Returns a version of this PeriodType instance that does not support hours.
757 *
758 * @return a new period type that supports the original set of fields except hours
759 */
760 public PeriodType withHoursRemoved() {
761 return withFieldRemoved(4, "NoHours");
762 }
763
764 /**
765 * Returns a version of this PeriodType instance that does not support minutes.
766 *
767 * @return a new period type that supports the original set of fields except minutes
768 */
769 public PeriodType withMinutesRemoved() {
770 return withFieldRemoved(5, "NoMinutes");
771 }
772
773 /**
774 * Returns a version of this PeriodType instance that does not support seconds.
775 *
776 * @return a new period type that supports the original set of fields except seconds
777 */
778 public PeriodType withSecondsRemoved() {
779 return withFieldRemoved(6, "NoSeconds");
780 }
781
782 /**
783 * Returns a version of this PeriodType instance that does not support milliseconds.
784 *
785 * @return a new period type that supports the original set of fields except milliseconds
786 */
787 public PeriodType withMillisRemoved() {
788 return withFieldRemoved(7, "NoMillis");
789 }
790
791 /**
792 * Removes the field specified by indices index.
793 *
794 * @param indicesIndex the index to remove
795 * @param name the name addition
796 * @return the new type
797 */
798 private PeriodType withFieldRemoved(int indicesIndex, String name) {
799 int fieldIndex = iIndices[indicesIndex];
800 if (fieldIndex == -1) {
801 return this;
802 }
803
804 DurationFieldType[] types = new DurationFieldType[size() - 1];
805 for (int i = 0; i < iTypes.length; i++) {
806 if (i < fieldIndex) {
807 types[i] = iTypes[i];
808 } else if (i > fieldIndex) {
809 types[i - 1] = iTypes[i];
810 }
811 }
812
813 int[] indices = new int[8];
814 for (int i = 0; i < indices.length; i++) {
815 if (i < indicesIndex) {
816 indices[i] = iIndices[i];
817 } else if (i > indicesIndex) {
818 indices[i] = (iIndices[i] == -1 ? -1 : iIndices[i] - 1);
819 } else {
820 indices[i] = -1;
821 }
822 }
823 return new PeriodType(getName() + name, types, indices);
824 }
825
826 //-----------------------------------------------------------------------
827 /**
828 * Compares this type to another object.
829 * To be equal, the object must be a PeriodType with the same set of fields.
830 *
831 * @param obj the object to compare to
832 * @return true if equal
833 */
834 public boolean equals(Object obj) {
835 if (this == obj) {
836 return true;
837 }
838 if (obj instanceof PeriodType == false) {
839 return false;
840 }
841 PeriodType other = (PeriodType) obj;
842 return (Arrays.equals(iTypes, other.iTypes));
843 }
844
845 /**
846 * Returns a hashcode based on the field types.
847 *
848 * @return a suitable hashcode
849 */
850 public int hashCode() {
851 int hash = 0;
852 for (int i = 0; i < iTypes.length; i++) {
853 hash += iTypes[i].hashCode();
854 }
855 return hash;
856 }
857
858 }