| /* |
| ****************************************************************************** |
| * Copyright (C) 2007, International Business Machines Corporation and * |
| * others. All Rights Reserved. * |
| ****************************************************************************** |
| */ |
| |
| package com.ibm.icu.impl.duration; |
| |
| import com.ibm.icu.impl.duration.impl.DataRecord; |
| import com.ibm.icu.impl.duration.impl.PeriodFormatterData; |
| import com.ibm.icu.impl.duration.impl.PeriodFormatterDataService; |
| |
| import java.util.TimeZone; |
| |
| /** |
| * Default implementation of PeriodBuilderFactory. This creates builders that |
| * use approximate durations. |
| */ |
| class BasicPeriodBuilderFactory implements PeriodBuilderFactory { |
| private PeriodFormatterDataService ds; |
| private Settings settings; |
| |
| private static final short allBits = 0xff; |
| |
| BasicPeriodBuilderFactory(PeriodFormatterDataService ds) { |
| this.ds = ds; |
| this.settings = new Settings(); |
| } |
| |
| static long approximateDurationOf(TimeUnit unit) { |
| return TimeUnit.approxDurations[unit.ordinal]; |
| } |
| |
| class Settings { |
| boolean inUse; |
| short uset = allBits; |
| TimeUnit maxUnit = TimeUnit.YEAR; |
| TimeUnit minUnit = TimeUnit.MILLISECOND; |
| int maxLimit; |
| int minLimit; |
| boolean allowZero = true; |
| boolean weeksAloneOnly; |
| boolean useMilliseconds = true; |
| |
| Settings setUnits(int uset) { |
| if (this.uset == uset) { |
| return this; |
| } |
| Settings result = inUse ? copy() : this; |
| |
| result.uset = (short)uset; |
| |
| if ((uset & allBits) == allBits) { |
| result.uset = allBits; |
| result.maxUnit = TimeUnit.YEAR; |
| result.minUnit = TimeUnit.MILLISECOND; |
| } else { |
| int lastUnit = -1; |
| for (int i = 0; i < TimeUnit.units.length; ++i) { |
| if (0 != (uset & (1 << i))) { |
| if (lastUnit == -1) { |
| result.maxUnit = TimeUnit.units[i]; |
| } |
| lastUnit = i; |
| } |
| } |
| if (lastUnit == -1) { |
| // currently empty, but this might be transient so no fail |
| result.minUnit = result.maxUnit = null; |
| } else { |
| result.minUnit = TimeUnit.units[lastUnit]; |
| } |
| } |
| |
| return result; |
| } |
| |
| short effectiveSet() { |
| if (useMilliseconds) { |
| return uset; |
| } |
| return (short)(uset & ~(1 << TimeUnit.MILLISECOND.ordinal)); |
| } |
| |
| Settings setMaxLimit(float maxLimit) { |
| int val = maxLimit <= 0 ? 0 : (int)(maxLimit*1000); |
| if (maxLimit == val) { |
| return this; |
| } |
| Settings result = inUse ? copy() : this; |
| result.maxLimit = val; |
| return result; |
| } |
| |
| Settings setMinLimit(float minLimit) { |
| int val = minLimit <= 0 ? 0 : (int)(minLimit*1000); |
| if (minLimit == val) { |
| return this; |
| } |
| Settings result = inUse ? copy() : this; |
| result.minLimit = val; |
| return result; |
| } |
| |
| Settings setAllowZero(boolean allow) { |
| if (this.allowZero == allow) { |
| return this; |
| } |
| Settings result = inUse ? copy() : this; |
| result.allowZero = allow; |
| return result; |
| } |
| |
| Settings setWeeksAloneOnly(boolean weeksAlone) { |
| if (this.weeksAloneOnly == weeksAlone) { |
| return this; |
| } |
| Settings result = inUse ? copy() : this; |
| result.weeksAloneOnly = weeksAlone; |
| return result; |
| } |
| |
| Settings setAllowMilliseconds(boolean useMilliseconds) { |
| if (this.useMilliseconds == useMilliseconds) { |
| return this; |
| } |
| Settings result = inUse ? copy() : this; |
| result.useMilliseconds = useMilliseconds; |
| return result; |
| } |
| |
| Settings setLocale(String localeName) { |
| PeriodFormatterData data = ds.get(localeName); |
| return this |
| .setAllowZero(data.allowZero()) |
| .setWeeksAloneOnly(data.weeksAloneOnly()) |
| .setAllowMilliseconds(data.useMilliseconds() != DataRecord.EMilliSupport.NO); |
| } |
| |
| Settings setInUse() { |
| inUse = true; |
| return this; |
| } |
| |
| Period createLimited(long duration, boolean inPast) { |
| long maxUnitDuration = approximateDurationOf(maxUnit); |
| if (maxLimit > 0 && duration * 1000 > maxLimit * maxUnitDuration) { |
| return Period.moreThan(maxLimit/1000f, maxUnit).inPast(inPast); |
| } |
| long minUnitDuration = approximateDurationOf(minUnit); |
| if (minLimit > 0 && duration * 1000 < minLimit * minUnitDuration) { |
| return Period.lessThan(minLimit/1000f, minUnit).inPast(inPast); |
| } |
| return null; |
| } |
| |
| public Settings copy() { |
| Settings result = new Settings(); |
| result.inUse = inUse; |
| result.uset = uset; |
| result.maxUnit = maxUnit; |
| result.minUnit = minUnit; |
| result.maxLimit = maxLimit; |
| result.minLimit = minLimit; |
| result.allowZero = allowZero; |
| result.weeksAloneOnly = weeksAloneOnly; |
| result.useMilliseconds = useMilliseconds; |
| return result; |
| } |
| } |
| |
| public PeriodBuilderFactory setAvailableUnitRange(TimeUnit minUnit, |
| TimeUnit maxUnit) { |
| int uset = 0; |
| for (int i = maxUnit.ordinal; i <= minUnit.ordinal; ++i) { |
| uset |= 1 << i; |
| } |
| if (uset == 0) { |
| throw new IllegalArgumentException("range " + minUnit + " to " + maxUnit + " is empty"); |
| } |
| settings = settings.setUnits(uset); |
| return this; |
| } |
| |
| public PeriodBuilderFactory setUnitIsAvailable(TimeUnit unit, |
| boolean available) { |
| int uset = settings.uset; |
| if (available) { |
| uset |= 1 << unit.ordinal; |
| } else { |
| uset &= ~(1 << unit.ordinal); |
| } |
| settings = settings.setUnits(uset); |
| return this; |
| } |
| |
| public PeriodBuilderFactory setMaxLimit(float maxLimit) { |
| settings = settings.setMaxLimit(maxLimit); |
| return this; |
| } |
| |
| public PeriodBuilderFactory setMinLimit(float minLimit) { |
| settings = settings.setMinLimit(minLimit); |
| return this; |
| } |
| |
| public PeriodBuilderFactory setAllowZero(boolean allow) { |
| settings = settings.setAllowZero(allow); |
| return this; |
| } |
| |
| public PeriodBuilderFactory setWeeksAloneOnly(boolean aloneOnly) { |
| settings = settings.setWeeksAloneOnly(aloneOnly); |
| return this; |
| } |
| |
| public PeriodBuilderFactory setAllowMilliseconds(boolean useMilliseconds) { |
| settings = settings.setAllowMilliseconds(useMilliseconds); |
| return this; |
| } |
| |
| public PeriodBuilderFactory setLocale(String localeName) { |
| settings = settings.setLocale(localeName); |
| return this; |
| } |
| |
| private Settings getSettings() { |
| if (settings.effectiveSet() == 0) { |
| return null; |
| } |
| return settings.setInUse(); |
| } |
| |
| /** |
| * Return a builder that represents relative time in terms of the single |
| * given TimeUnit |
| * |
| * @param unit the single TimeUnit with which to represent times |
| * @return a builder |
| */ |
| public PeriodBuilder getFixedUnitBuilder(TimeUnit unit) { |
| return FixedUnitBuilder.get(unit, getSettings()); |
| } |
| |
| /** |
| * Return a builder that represents relative time in terms of the |
| * largest period less than or equal to the duration. |
| * |
| * @return a builder |
| */ |
| public PeriodBuilder getSingleUnitBuilder() { |
| return SingleUnitBuilder.get(getSettings()); |
| } |
| |
| /** |
| * Return a builder that formats the largest one or two periods, |
| * Starting with the largest period less than or equal to the duration. |
| * It formats two periods if the first period has a count < 2 |
| * and the next period has a count >= 1. |
| * |
| * @return a builder |
| */ |
| public PeriodBuilder getOneOrTwoUnitBuilder() { |
| return OneOrTwoUnitBuilder.get(getSettings()); |
| } |
| |
| /** |
| * Return a builder that formats the given number of periods, |
| * starting with the largest period less than or equal to the |
| * duration. |
| * |
| * @return a builder |
| */ |
| public PeriodBuilder getMultiUnitBuilder(int periodCount) { |
| return MultiUnitBuilder.get(periodCount, getSettings()); |
| } |
| } |
| |
| abstract class PeriodBuilderImpl implements PeriodBuilder { |
| |
| protected BasicPeriodBuilderFactory.Settings settings; |
| |
| public Period create(long duration) { |
| return createWithReferenceDate(duration, System.currentTimeMillis()); |
| } |
| |
| public long approximateDurationOf(TimeUnit unit) { |
| return BasicPeriodBuilderFactory.approximateDurationOf(unit); |
| } |
| |
| public Period createWithReferenceDate(long duration, long referenceDate) { |
| boolean inPast = duration < 0; |
| if (inPast) { |
| duration = -duration; |
| } |
| Period ts = settings.createLimited(duration, inPast); |
| if (ts == null) { |
| ts = handleCreate(duration, referenceDate, inPast); |
| if (ts == null) { |
| ts = Period.lessThan(1, settings.minUnit).inPast(inPast); |
| } |
| } |
| return ts; |
| } |
| |
| public PeriodBuilder withTimeZone(TimeZone timeZone) { |
| // ignore the time zone |
| return this; |
| } |
| |
| public PeriodBuilder withLocale(String localeName) { |
| BasicPeriodBuilderFactory.Settings newSettings = settings.setLocale(localeName); |
| if (newSettings != settings) { |
| return withSettings(newSettings); |
| } |
| return this; |
| } |
| |
| protected abstract PeriodBuilder withSettings(BasicPeriodBuilderFactory.Settings settings); |
| |
| protected abstract Period handleCreate(long duration, long referenceDate, |
| boolean inPast); |
| |
| protected PeriodBuilderImpl(BasicPeriodBuilderFactory.Settings settings) { |
| this.settings = settings; |
| } |
| } |
| |
| class FixedUnitBuilder extends PeriodBuilderImpl { |
| private TimeUnit unit; |
| |
| public static FixedUnitBuilder get(TimeUnit unit, BasicPeriodBuilderFactory.Settings settings) { |
| if (settings != null && (settings.effectiveSet() & (1 << unit.ordinal)) != 0) { |
| return new FixedUnitBuilder(unit, settings); |
| } |
| return null; |
| } |
| |
| FixedUnitBuilder(TimeUnit unit, BasicPeriodBuilderFactory.Settings settings) { |
| super(settings); |
| this.unit = unit; |
| } |
| |
| protected PeriodBuilder withSettings(BasicPeriodBuilderFactory.Settings settings) { |
| return get(unit, settings); |
| } |
| |
| protected Period handleCreate(long duration, long referenceDate, |
| boolean inPast) { |
| if (unit == null) { |
| return null; |
| } |
| long unitDuration = approximateDurationOf(unit); |
| return Period.at((float)((double)duration/unitDuration), unit) |
| .inPast(inPast); |
| } |
| } |
| |
| class SingleUnitBuilder extends PeriodBuilderImpl { |
| SingleUnitBuilder(BasicPeriodBuilderFactory.Settings settings) { |
| super(settings); |
| } |
| |
| public static SingleUnitBuilder get(BasicPeriodBuilderFactory.Settings settings) { |
| if (settings == null) { |
| return null; |
| } |
| return new SingleUnitBuilder(settings); |
| } |
| |
| protected PeriodBuilder withSettings(BasicPeriodBuilderFactory.Settings settings) { |
| return SingleUnitBuilder.get(settings); |
| } |
| |
| protected Period handleCreate(long duration, long referenceDate, |
| boolean inPast) { |
| short uset = settings.effectiveSet(); |
| for (int i = 0; i < TimeUnit.units.length; ++i) { |
| if (0 != (uset & (1 << i))) { |
| TimeUnit unit = TimeUnit.units[i]; |
| long unitDuration = approximateDurationOf(unit); |
| if (duration >= unitDuration) { |
| return Period.at((float)((double)duration/unitDuration), unit) |
| .inPast(inPast); |
| } |
| } |
| } |
| return null; |
| } |
| } |
| |
| class OneOrTwoUnitBuilder extends PeriodBuilderImpl { |
| OneOrTwoUnitBuilder(BasicPeriodBuilderFactory.Settings settings) { |
| super(settings); |
| } |
| |
| public static OneOrTwoUnitBuilder get(BasicPeriodBuilderFactory.Settings settings) { |
| if (settings == null) { |
| return null; |
| } |
| return new OneOrTwoUnitBuilder(settings); |
| } |
| |
| protected PeriodBuilder withSettings(BasicPeriodBuilderFactory.Settings settings) { |
| return OneOrTwoUnitBuilder.get(settings); |
| } |
| |
| protected Period handleCreate(long duration, long referenceDate, |
| boolean inPast) { |
| Period period = null; |
| short uset = settings.effectiveSet(); |
| for (int i = 0; i < TimeUnit.units.length; ++i) { |
| if (0 != (uset & (1 << i))) { |
| TimeUnit unit = TimeUnit.units[i]; |
| long unitDuration = approximateDurationOf(unit); |
| if (duration >= unitDuration || period != null) { |
| double count = (double)duration/unitDuration; |
| if (period == null) { |
| if (count >= 2) { |
| period = Period.at((float)count, unit); |
| break; |
| } |
| period = Period.at(1, unit).inPast(inPast); |
| duration -= unitDuration; |
| } else { |
| if (count >= 1) { |
| period.and((float)count, unit); |
| } |
| break; |
| } |
| } |
| } |
| } |
| return period; |
| } |
| } |
| |
| class MultiUnitBuilder extends PeriodBuilderImpl { |
| private int nPeriods; |
| |
| MultiUnitBuilder(int nPeriods, BasicPeriodBuilderFactory.Settings settings) { |
| super(settings); |
| this.nPeriods = nPeriods; |
| } |
| |
| public static MultiUnitBuilder get(int nPeriods, BasicPeriodBuilderFactory.Settings settings) { |
| if (nPeriods > 0 && settings != null) { |
| return new MultiUnitBuilder(nPeriods, settings); |
| } |
| return null; |
| } |
| |
| protected PeriodBuilder withSettings(BasicPeriodBuilderFactory.Settings settings) { |
| return MultiUnitBuilder.get(nPeriods, settings); |
| } |
| |
| protected Period handleCreate(long duration, long referenceDate, |
| boolean inPast) { |
| Period period = null; |
| int n = 0; |
| short uset = settings.effectiveSet(); |
| for (int i = 0; i < TimeUnit.units.length; ++i) { |
| if (0 != (uset & (1 << i))) { |
| TimeUnit unit = TimeUnit.units[i]; |
| if (n == nPeriods) { |
| break; |
| } |
| long unitDuration = approximateDurationOf(unit); |
| if (duration >= unitDuration || n > 0) { |
| ++n; |
| double count = (double)duration / unitDuration; |
| if (n < nPeriods) { |
| count = Math.floor(count); |
| duration -= (long)(count * unitDuration); |
| } |
| if (period == null) { |
| period = Period.at((float)count, unit).inPast(inPast); |
| } else { |
| period.and((float)count, unit); |
| } |
| } |
| } |
| } |
| return period; |
| } |
| } |
| |