1/*
2******************************************************************************
3* Copyright (C) 2007-2010, International Business Machines Corporation and   *
4* others. All Rights Reserved.                                               *
5******************************************************************************
6*/
7
8package com.ibm.icu.impl.duration;
9
10import com.ibm.icu.impl.duration.BasicPeriodFormatterFactory.Customizations;
11import com.ibm.icu.impl.duration.impl.DataRecord.ECountVariant;
12import com.ibm.icu.impl.duration.impl.DataRecord.EMilliSupport;
13import com.ibm.icu.impl.duration.impl.DataRecord.ESeparatorVariant;
14import com.ibm.icu.impl.duration.impl.DataRecord.ETimeDirection;
15import com.ibm.icu.impl.duration.impl.DataRecord.ETimeLimit;
16import com.ibm.icu.impl.duration.impl.PeriodFormatterData;
17
18/**
19 * Core implementation class for PeriodFormatter.
20 */
21class BasicPeriodFormatter implements PeriodFormatter {
22  private BasicPeriodFormatterFactory factory;
23  private String localeName;
24  private PeriodFormatterData data;
25  private Customizations customs;
26
27  BasicPeriodFormatter(BasicPeriodFormatterFactory factory,
28                       String localeName,
29                       PeriodFormatterData data,
30                       Customizations customs) {
31    this.factory = factory;
32    this.localeName = localeName;
33    this.data = data;
34    this.customs = customs;
35  }
36
37  public String format(Period period) {
38    if (!period.isSet()) {
39      throw new IllegalArgumentException("period is not set");
40    }
41    return format(period.timeLimit, period.inFuture, period.counts);
42  }
43
44  public PeriodFormatter withLocale(String locName) {
45    if (!this.localeName.equals(locName)) {
46      PeriodFormatterData newData = factory.getData(locName);
47      return new BasicPeriodFormatter(factory, locName, newData,
48                                      customs);
49    }
50    return this;
51  }
52
53  private String format(int tl, boolean inFuture, int[] counts) {
54    int mask = 0;
55    for (int i = 0; i < counts.length; ++i) {
56      if (counts[i] > 0) {
57        mask |= 1 << i;
58      }
59    }
60
61    // if the data does not allow formatting of zero periods,
62    // remove these from consideration.  If the result has no
63    // periods set, return null to indicate we could not format
64    // the duration.
65    if (!data.allowZero()) {
66      for (int i = 0, m = 1; i < counts.length; ++i, m <<= 1) {
67        if ((mask & m) != 0 && counts[i] == 1) {
68          mask &= ~m;
69        }
70      }
71      if (mask == 0) {
72        return null;
73      }
74    }
75
76    // if the data does not allow milliseconds but milliseconds are
77    // set, merge them with seconds and force display of seconds to
78    // decimal with 3 places.
79    boolean forceD3Seconds = false;
80    if (data.useMilliseconds() != EMilliSupport.YES &&
81        (mask & (1 << TimeUnit.MILLISECOND.ordinal)) != 0) {
82      int sx = TimeUnit.SECOND.ordinal;
83      int mx = TimeUnit.MILLISECOND.ordinal;
84      int sf = 1 << sx;
85      int mf = 1 << mx;
86      switch (data.useMilliseconds()) {
87        case EMilliSupport.WITH_SECONDS: {
88          // if there are seconds, merge with seconds, otherwise leave alone
89          if ((mask & sf) != 0) {
90            counts[sx] += (counts[mx]-1)/1000;
91            mask &= ~mf;
92            forceD3Seconds = true;
93          }
94        } break;
95        case EMilliSupport.NO: {
96          // merge with seconds, reset seconds before use just in case
97          if ((mask & sf) == 0) {
98            mask |= sf;
99            counts[sx] = 1;
100          }
101          counts[sx] += (counts[mx]-1)/1000;
102          mask &= ~mf;
103          forceD3Seconds = true;
104        } break;
105      }
106    }
107
108    // get the first and last units that are set.
109    int first = 0;
110    int last = counts.length - 1;
111    while (first < counts.length && (mask & (1 << first)) == 0) ++first;
112    while (last > first && (mask & (1 << last)) == 0) --last;
113
114    // determine if there is any non-zero unit
115    boolean isZero = true;
116    for (int i = first; i <= last; ++i) {
117      if (((mask & (1 << i)) != 0) &&  counts[i] > 1) {
118        isZero = false;
119        break;
120      }
121    }
122
123    StringBuffer sb = new StringBuffer();
124
125    // if we've been requested to not display a limit, or there are
126    // no non-zero units, do not display the limit.
127    if (!customs.displayLimit || isZero) {
128      tl = ETimeLimit.NOLIMIT;
129    }
130
131    // if we've been requested to not display the direction, or there
132    // are no non-zero units, do not display the direction.
133    int td;
134    if (!customs.displayDirection || isZero) {
135      td = ETimeDirection.NODIRECTION;
136    } else {
137      td = inFuture ? ETimeDirection.FUTURE : ETimeDirection.PAST;
138    }
139
140    // format the initial portion of the string before the units.
141    // record whether we need to use a digit prefix (because the
142    // initial portion forces it)
143    boolean useDigitPrefix = data.appendPrefix(tl, td, sb);
144
145    // determine some formatting params and initial values
146    boolean multiple = first != last;
147    boolean wasSkipped = true; // no initial skip marker
148    boolean skipped = false;
149    boolean countSep = customs.separatorVariant != ESeparatorVariant.NONE;
150
151    // loop for formatting the units
152    for (int i = first, j = i; i <= last; i = j) {
153      if (skipped) {
154        // we didn't format the previous unit
155        data.appendSkippedUnit(sb);
156        skipped = false;
157        wasSkipped = true;
158      }
159
160      while (++j < last && (mask & (1 << j)) == 0) {
161        skipped = true; // skip
162      }
163
164      TimeUnit unit = TimeUnit.units[i];
165      int count = counts[i] - 1;
166
167      int cv = customs.countVariant;
168      if (i == last) {
169        if (forceD3Seconds) {
170          cv = ECountVariant.DECIMAL3;
171        }
172        // else leave unchanged
173      } else {
174        cv = ECountVariant.INTEGER;
175      }
176      boolean isLast = i == last;
177      boolean mustSkip = data.appendUnit(unit, count, cv, customs.unitVariant,
178                                         countSep, useDigitPrefix, multiple, isLast, wasSkipped, sb);
179      skipped |= mustSkip;
180      wasSkipped = false;
181
182      if (customs.separatorVariant != ESeparatorVariant.NONE && j <= last) {
183        boolean afterFirst = i == first;
184        boolean beforeLast = j == last;
185        boolean fullSep = customs.separatorVariant == ESeparatorVariant.FULL;
186        useDigitPrefix = data.appendUnitSeparator(unit, fullSep, afterFirst, beforeLast, sb);
187      } else {
188        useDigitPrefix = false;
189      }
190    }
191    data.appendSuffix(tl, td, sb);
192
193    return sb.toString();
194  }
195}
196