Java tutorial
/* * Copyright 2001-2009 Stephen Colebourne * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.joda.time; import java.io.Serializable; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; import org.joda.time.field.FieldUtils; /** * Controls a period implementation by specifying which duration fields are to be used. * <p> * The following implementations are provided: * <ul> * <li>Standard - years, months, weeks, days, hours, minutes, seconds, millis * <li>YearMonthDayTime - years, months, days, hours, minutes, seconds, millis * <li>YearMonthDay - years, months, days * <li>YearWeekDayTime - years, weeks, days, hours, minutes, seconds, millis * <li>YearWeekDay - years, weeks, days * <li>YearDayTime - years, days, hours, minutes, seconds, millis * <li>YearDay - years, days, hours * <li>DayTime - days, hours, minutes, seconds, millis * <li>Time - hours, minutes, seconds, millis * <li>plus one for each single type * </ul> * * <p> * PeriodType is thread-safe and immutable, and all subclasses must be as well. * * @author Brian S O'Neill * @author Stephen Colebourne * @since 1.0 */ public class PeriodType implements Serializable { /** Serialization version */ private static final long serialVersionUID = 2274324892792009998L; /** Cache of all the known types. */ private static final Map<PeriodType, Object> cTypes = new HashMap<PeriodType, Object>(32); static int YEAR_INDEX = 0; static int MONTH_INDEX = 1; static int WEEK_INDEX = 2; static int DAY_INDEX = 3; static int HOUR_INDEX = 4; static int MINUTE_INDEX = 5; static int SECOND_INDEX = 6; static int MILLI_INDEX = 7; private static PeriodType cStandard; private static PeriodType cYMDTime; private static PeriodType cYMD; private static PeriodType cYWDTime; private static PeriodType cYWD; private static PeriodType cYDTime; private static PeriodType cYD; private static PeriodType cDTime; private static PeriodType cTime; private static PeriodType cYears; private static PeriodType cMonths; private static PeriodType cWeeks; private static PeriodType cDays; private static PeriodType cHours; private static PeriodType cMinutes; private static PeriodType cSeconds; private static PeriodType cMillis; /** * Gets a type that defines all standard fields. * <ul> * <li>years * <li>months * <li>weeks * <li>days * <li>hours * <li>minutes * <li>seconds * <li>milliseconds * </ul> * * @return the period type */ public static PeriodType standard() { PeriodType type = cStandard; if (type == null) { type = new PeriodType("Standard", new DurationFieldType[] { DurationFieldType.years(), DurationFieldType.months(), DurationFieldType.weeks(), DurationFieldType.days(), DurationFieldType.hours(), DurationFieldType.minutes(), DurationFieldType.seconds(), DurationFieldType.millis(), }, new int[] { 0, 1, 2, 3, 4, 5, 6, 7, }); cStandard = type; } return type; } /** * Gets a type that defines all standard fields except weeks. * <ul> * <li>years * <li>months * <li>days * <li>hours * <li>minutes * <li>seconds * <li>milliseconds * </ul> * * @return the period type */ public static PeriodType yearMonthDayTime() { PeriodType type = cYMDTime; if (type == null) { type = new PeriodType("YearMonthDayTime", new DurationFieldType[] { DurationFieldType.years(), DurationFieldType.months(), DurationFieldType.days(), DurationFieldType.hours(), DurationFieldType.minutes(), DurationFieldType.seconds(), DurationFieldType.millis(), }, new int[] { 0, 1, -1, 2, 3, 4, 5, 6, }); cYMDTime = type; } return type; } /** * Gets a type that defines the year, month and day fields. * <ul> * <li>years * <li>months * <li>days * </ul> * * @return the period type * @since 1.1 */ public static PeriodType yearMonthDay() { PeriodType type = cYMD; if (type == null) { type = new PeriodType("YearMonthDay", new DurationFieldType[] { DurationFieldType.years(), DurationFieldType.months(), DurationFieldType.days(), }, new int[] { 0, 1, -1, 2, -1, -1, -1, -1, }); cYMD = type; } return type; } /** * Gets a type that defines all standard fields except months. * <ul> * <li>years * <li>weeks * <li>days * <li>hours * <li>minutes * <li>seconds * <li>milliseconds * </ul> * * @return the period type */ public static PeriodType yearWeekDayTime() { PeriodType type = cYWDTime; if (type == null) { type = new PeriodType("YearWeekDayTime", new DurationFieldType[] { DurationFieldType.years(), DurationFieldType.weeks(), DurationFieldType.days(), DurationFieldType.hours(), DurationFieldType.minutes(), DurationFieldType.seconds(), DurationFieldType.millis(), }, new int[] { 0, -1, 1, 2, 3, 4, 5, 6, }); cYWDTime = type; } return type; } /** * Gets a type that defines year, week and day fields. * <ul> * <li>years * <li>weeks * <li>days * </ul> * * @return the period type * @since 1.1 */ public static PeriodType yearWeekDay() { PeriodType type = cYWD; if (type == null) { type = new PeriodType("YearWeekDay", new DurationFieldType[] { DurationFieldType.years(), DurationFieldType.weeks(), DurationFieldType.days(), }, new int[] { 0, -1, 1, 2, -1, -1, -1, -1, }); cYWD = type; } return type; } /** * Gets a type that defines all standard fields except months and weeks. * <ul> * <li>years * <li>days * <li>hours * <li>minutes * <li>seconds * <li>milliseconds * </ul> * * @return the period type */ public static PeriodType yearDayTime() { PeriodType type = cYDTime; if (type == null) { type = new PeriodType("YearDayTime", new DurationFieldType[] { DurationFieldType.years(), DurationFieldType.days(), DurationFieldType.hours(), DurationFieldType.minutes(), DurationFieldType.seconds(), DurationFieldType.millis(), }, new int[] { 0, -1, -1, 1, 2, 3, 4, 5, }); cYDTime = type; } return type; } /** * Gets a type that defines the year and day fields. * <ul> * <li>years * <li>days * </ul> * * @return the period type * @since 1.1 */ public static PeriodType yearDay() { PeriodType type = cYD; if (type == null) { type = new PeriodType("YearDay", new DurationFieldType[] { DurationFieldType.years(), DurationFieldType.days(), }, new int[] { 0, -1, -1, 1, -1, -1, -1, -1, }); cYD = type; } return type; } /** * Gets a type that defines all standard fields from days downwards. * <ul> * <li>days * <li>hours * <li>minutes * <li>seconds * <li>milliseconds * </ul> * * @return the period type */ public static PeriodType dayTime() { PeriodType type = cDTime; if (type == null) { type = new PeriodType("DayTime", new DurationFieldType[] { DurationFieldType.days(), DurationFieldType.hours(), DurationFieldType.minutes(), DurationFieldType.seconds(), DurationFieldType.millis(), }, new int[] { -1, -1, -1, 0, 1, 2, 3, 4, }); cDTime = type; } return type; } /** * Gets a type that defines all standard time fields. * <ul> * <li>hours * <li>minutes * <li>seconds * <li>milliseconds * </ul> * * @return the period type */ public static PeriodType time() { PeriodType type = cTime; if (type == null) { type = new PeriodType("Time", new DurationFieldType[] { DurationFieldType.hours(), DurationFieldType.minutes(), DurationFieldType.seconds(), DurationFieldType.millis(), }, new int[] { -1, -1, -1, -1, 0, 1, 2, 3, }); cTime = type; } return type; } /** * Gets a type that defines just the years field. * * @return the period type */ public static PeriodType years() { PeriodType type = cYears; if (type == null) { type = new PeriodType("Years", new DurationFieldType[] { DurationFieldType.years() }, new int[] { 0, -1, -1, -1, -1, -1, -1, -1, }); cYears = type; } return type; } /** * Gets a type that defines just the months field. * * @return the period type */ public static PeriodType months() { PeriodType type = cMonths; if (type == null) { type = new PeriodType("Months", new DurationFieldType[] { DurationFieldType.months() }, new int[] { -1, 0, -1, -1, -1, -1, -1, -1, }); cMonths = type; } return type; } /** * Gets a type that defines just the weeks field. * * @return the period type */ public static PeriodType weeks() { PeriodType type = cWeeks; if (type == null) { type = new PeriodType("Weeks", new DurationFieldType[] { DurationFieldType.weeks() }, new int[] { -1, -1, 0, -1, -1, -1, -1, -1, }); cWeeks = type; } return type; } /** * Gets a type that defines just the days field. * * @return the period type */ public static PeriodType days() { PeriodType type = cDays; if (type == null) { type = new PeriodType("Days", new DurationFieldType[] { DurationFieldType.days() }, new int[] { -1, -1, -1, 0, -1, -1, -1, -1, }); cDays = type; } return type; } /** * Gets a type that defines just the hours field. * * @return the period type */ public static PeriodType hours() { PeriodType type = cHours; if (type == null) { type = new PeriodType("Hours", new DurationFieldType[] { DurationFieldType.hours() }, new int[] { -1, -1, -1, -1, 0, -1, -1, -1, }); cHours = type; } return type; } /** * Gets a type that defines just the minutes field. * * @return the period type */ public static PeriodType minutes() { PeriodType type = cMinutes; if (type == null) { type = new PeriodType("Minutes", new DurationFieldType[] { DurationFieldType.minutes() }, new int[] { -1, -1, -1, -1, -1, 0, -1, -1, }); cMinutes = type; } return type; } /** * Gets a type that defines just the seconds field. * * @return the period type */ public static PeriodType seconds() { PeriodType type = cSeconds; if (type == null) { type = new PeriodType("Seconds", new DurationFieldType[] { DurationFieldType.seconds() }, new int[] { -1, -1, -1, -1, -1, -1, 0, -1, }); cSeconds = type; } return type; } /** * Gets a type that defines just the millis field. * * @return the period type */ public static PeriodType millis() { PeriodType type = cMillis; if (type == null) { type = new PeriodType("Millis", new DurationFieldType[] { DurationFieldType.millis() }, new int[] { -1, -1, -1, -1, -1, -1, -1, 0, }); cMillis = type; } return type; } /** * Gets a period type that contains the duration types of the array. * <p> * Only the 8 standard duration field types are supported. * * @param types the types to include in the array. * @return the period type * @since 1.1 */ public static synchronized PeriodType forFields(DurationFieldType[] types) { if (types == null || types.length == 0) { throw new IllegalArgumentException("Types array must not be null or empty"); } for (int i = 0; i < types.length; i++) { if (types[i] == null) { throw new IllegalArgumentException("Types array must not contain null"); } } Map<PeriodType, Object> cache = cTypes; if (cache.isEmpty()) { cache.put(standard(), standard()); cache.put(yearMonthDayTime(), yearMonthDayTime()); cache.put(yearMonthDay(), yearMonthDay()); cache.put(yearWeekDayTime(), yearWeekDayTime()); cache.put(yearWeekDay(), yearWeekDay()); cache.put(yearDayTime(), yearDayTime()); cache.put(yearDay(), yearDay()); cache.put(dayTime(), dayTime()); cache.put(time(), time()); cache.put(years(), years()); cache.put(months(), months()); cache.put(weeks(), weeks()); cache.put(days(), days()); cache.put(hours(), hours()); cache.put(minutes(), minutes()); cache.put(seconds(), seconds()); cache.put(millis(), millis()); } PeriodType inPartType = new PeriodType(null, types, null); Object cached = cache.get(inPartType); if (cached instanceof PeriodType) { return (PeriodType) cached; } if (cached != null) { throw new IllegalArgumentException("PeriodType does not support fields: " + cached); } PeriodType type = standard(); List<DurationFieldType> list = new ArrayList<DurationFieldType>(Arrays.asList(types)); if (list.remove(DurationFieldType.years()) == false) { type = type.withYearsRemoved(); } if (list.remove(DurationFieldType.months()) == false) { type = type.withMonthsRemoved(); } if (list.remove(DurationFieldType.weeks()) == false) { type = type.withWeeksRemoved(); } if (list.remove(DurationFieldType.days()) == false) { type = type.withDaysRemoved(); } if (list.remove(DurationFieldType.hours()) == false) { type = type.withHoursRemoved(); } if (list.remove(DurationFieldType.minutes()) == false) { type = type.withMinutesRemoved(); } if (list.remove(DurationFieldType.seconds()) == false) { type = type.withSecondsRemoved(); } if (list.remove(DurationFieldType.millis()) == false) { type = type.withMillisRemoved(); } if (list.size() > 0) { cache.put(inPartType, list); throw new IllegalArgumentException("PeriodType does not support fields: " + list); } // recheck cache in case initial array order was wrong PeriodType checkPartType = new PeriodType(null, type.iTypes, null); PeriodType checkedType = (PeriodType) cache.get(checkPartType); if (checkedType != null) { cache.put(checkPartType, checkedType); return checkedType; } cache.put(checkPartType, type); return type; } //----------------------------------------------------------------------- /** The name of the type */ private final String iName; /** The array of types */ private final DurationFieldType[] iTypes; /** The array of indices */ private final int[] iIndices; /** * Constructor. * * @param name the name * @param types the types * @param indices the indices */ protected PeriodType(String name, DurationFieldType[] types, int[] indices) { super(); iName = name; iTypes = types; iIndices = indices; } //----------------------------------------------------------------------- /** * Gets the name of the period type. * * @return the name */ public String getName() { return iName; } /** * Gets the number of fields in the period type. * * @return the number of fields */ public int size() { return iTypes.length; } /** * Gets the field type by index. * * @param index the index to retrieve * @return the field type * @throws IndexOutOfBoundsException if the index is invalid */ public DurationFieldType getFieldType(int index) { return iTypes[index]; } /** * Checks whether the field specified is supported by this period. * * @param type the type to check, may be null which returns false * @return true if the field is supported */ public boolean isSupported(DurationFieldType type) { return (indexOf(type) >= 0); } /** * Gets the index of the field in this period. * * @param type the type to check, may be null which returns -1 * @return the index of -1 if not supported */ public int indexOf(DurationFieldType type) { for (int i = 0, isize = size(); i < isize; i++) { if (iTypes[i] == type) { return i; } } return -1; } /** * Gets a debugging to string. * * @return a string */ public String toString() { return "PeriodType[" + getName() + "]"; } //----------------------------------------------------------------------- /** * Gets the indexed field part of the period. * * @param period the period to query * @param index the index to use * @return the value of the field, zero if unsupported */ int getIndexedField(ReadablePeriod period, int index) { int realIndex = iIndices[index]; return (realIndex == -1 ? 0 : period.getValue(realIndex)); } /** * Sets the indexed field part of the period. * * @param period the period to query * @param index the index to use * @param values the array to populate * @param newValue the value to set * @throws UnsupportedOperationException if not supported */ boolean setIndexedField(ReadablePeriod period, int index, int[] values, int newValue) { int realIndex = iIndices[index]; if (realIndex == -1) { throw new UnsupportedOperationException("Field is not supported"); } values[realIndex] = newValue; return true; } /** * Adds to the indexed field part of the period. * * @param period the period to query * @param index the index to use * @param values the array to populate * @param valueToAdd the value to add * @return true if the array is updated * @throws UnsupportedOperationException if not supported */ boolean addIndexedField(ReadablePeriod period, int index, int[] values, int valueToAdd) { if (valueToAdd == 0) { return false; } int realIndex = iIndices[index]; if (realIndex == -1) { throw new UnsupportedOperationException("Field is not supported"); } values[realIndex] = FieldUtils.safeAdd(values[realIndex], valueToAdd); return true; } //----------------------------------------------------------------------- /** * Returns a version of this PeriodType instance that does not support years. * * @return a new period type that supports the original set of fields except years */ public PeriodType withYearsRemoved() { return withFieldRemoved(0, "NoYears"); } /** * Returns a version of this PeriodType instance that does not support months. * * @return a new period type that supports the original set of fields except months */ public PeriodType withMonthsRemoved() { return withFieldRemoved(1, "NoMonths"); } /** * Returns a version of this PeriodType instance that does not support weeks. * * @return a new period type that supports the original set of fields except weeks */ public PeriodType withWeeksRemoved() { return withFieldRemoved(2, "NoWeeks"); } /** * Returns a version of this PeriodType instance that does not support days. * * @return a new period type that supports the original set of fields except days */ public PeriodType withDaysRemoved() { return withFieldRemoved(3, "NoDays"); } /** * Returns a version of this PeriodType instance that does not support hours. * * @return a new period type that supports the original set of fields except hours */ public PeriodType withHoursRemoved() { return withFieldRemoved(4, "NoHours"); } /** * Returns a version of this PeriodType instance that does not support minutes. * * @return a new period type that supports the original set of fields except minutes */ public PeriodType withMinutesRemoved() { return withFieldRemoved(5, "NoMinutes"); } /** * Returns a version of this PeriodType instance that does not support seconds. * * @return a new period type that supports the original set of fields except seconds */ public PeriodType withSecondsRemoved() { return withFieldRemoved(6, "NoSeconds"); } /** * Returns a version of this PeriodType instance that does not support milliseconds. * * @return a new period type that supports the original set of fields except milliseconds */ public PeriodType withMillisRemoved() { return withFieldRemoved(7, "NoMillis"); } /** * Removes the field specified by indices index. * * @param indicesIndex the index to remove * @param name the name addition * @return the new type */ private PeriodType withFieldRemoved(int indicesIndex, String name) { int fieldIndex = iIndices[indicesIndex]; if (fieldIndex == -1) { return this; } DurationFieldType[] types = new DurationFieldType[size() - 1]; for (int i = 0; i < iTypes.length; i++) { if (i < fieldIndex) { types[i] = iTypes[i]; } else if (i > fieldIndex) { types[i - 1] = iTypes[i]; } } int[] indices = new int[8]; for (int i = 0; i < indices.length; i++) { if (i < indicesIndex) { indices[i] = iIndices[i]; } else if (i > indicesIndex) { indices[i] = (iIndices[i] == -1 ? -1 : iIndices[i] - 1); } else { indices[i] = -1; } } return new PeriodType(getName() + name, types, indices); } //----------------------------------------------------------------------- /** * Compares this type to another object. * To be equal, the object must be a PeriodType with the same set of fields. * * @param obj the object to compare to * @return true if equal */ public boolean equals(Object obj) { if (this == obj) { return true; } if (obj instanceof PeriodType == false) { return false; } PeriodType other = (PeriodType) obj; return (Arrays.equals(iTypes, other.iTypes)); } /** * Returns a hashcode based on the field types. * * @return a suitable hashcode */ public int hashCode() { int hash = 0; for (int i = 0; i < iTypes.length; i++) { hash += iTypes[i].hashCode(); } return hash; } }