View Javadoc

1   /*
2    *  Copyright 2001-2005 Stephen Colebourne
3    *
4    *  Licensed under the Apache License, Version 2.0 (the "License");
5    *  you may not use this file except in compliance with the License.
6    *  You may obtain a copy of the License at
7    *
8    *      http://www.apache.org/licenses/LICENSE-2.0
9    *
10   *  Unless required by applicable law or agreed to in writing, software
11   *  distributed under the License is distributed on an "AS IS" BASIS,
12   *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   *  See the License for the specific language governing permissions and
14   *  limitations under the License.
15   */
16  package org.joda.time.format;
17  
18  import java.io.IOException;
19  import java.io.Writer;
20  import java.util.Locale;
21  
22  import org.joda.time.MutablePeriod;
23  import org.joda.time.Period;
24  import org.joda.time.PeriodType;
25  import org.joda.time.ReadWritablePeriod;
26  import org.joda.time.ReadablePeriod;
27  
28  /**
29   * Controls the printing and parsing of a time period to and from a string.
30   * <p>
31   * This class is the main API for printing and parsing used by most applications.
32   * Instances of this class are created via one of three factory classes:
33   * <ul>
34   * <li>{@link PeriodFormat} - formats by pattern and style</li>
35   * <li>{@link ISOPeriodFormat} - ISO8601 formats</li>
36   * <li>{@link PeriodFormatterBuilder} - complex formats created via method calls</li>
37   * </ul>
38   * <p>
39   * An instance of this class holds a reference internally to one printer and
40   * one parser. It is possible that one of these may be null, in which case the
41   * formatter cannot print/parse. This can be checked via the {@link #isPrinter()}
42   * and {@link #isParser()} methods.
43   * <p>
44   * The underlying printer/parser can be altered to behave exactly as required
45   * by using a decorator modifier:
46   * <ul>
47   * <li>{@link #withLocale(Locale)} - returns a new formatter that uses the specified locale</li>
48   * </ul>
49   * This returns a new formatter (instances of this class are immutable).
50   * <p>
51   * The main methods of the class are the <code>printXxx</code> and
52   * <code>parseXxx</code> methods. These are used as follows:
53   * <pre>
54   * // print using the default locale
55   * String periodStr = formatter.print(period);
56   * // print using the French locale
57   * String periodStr = formatter.withLocale(Locale.FRENCH).print(period);
58   * 
59   * // parse using the French locale
60   * Period date = formatter.withLocale(Locale.FRENCH).parsePeriod(str);
61   * </pre>
62   *
63   * @author Brian S O'Neill
64   * @author Stephen Colebourne
65   * @since 1.0
66   */
67  public class PeriodFormatter {
68  
69      /** The internal printer used to output the datetime. */
70      private final PeriodPrinter iPrinter;
71      /** The internal parser used to output the datetime. */
72      private final PeriodParser iParser;
73      /** The locale to use for printing and parsing. */
74      private final Locale iLocale;
75      /** The period type used in parsing. */
76      private final PeriodType iParseType;
77  
78      /**
79       * Creates a new formatter, however you will normally use the factory
80       * or the builder.
81       * 
82       * @param printer  the internal printer, null if cannot print
83       * @param parser  the internal parser, null if cannot parse
84       */
85      public PeriodFormatter(
86              PeriodPrinter printer, PeriodParser parser) {
87          super();
88          iPrinter = printer;
89          iParser = parser;
90          iLocale = null;
91          iParseType = null;
92      }
93  
94      /**
95       * Constructor.
96       * 
97       * @param printer  the internal printer, null if cannot print
98       * @param parser  the internal parser, null if cannot parse
99       * @param locale  the locale to use
100      * @param type  the parse period type
101      */
102     private PeriodFormatter(
103             PeriodPrinter printer, PeriodParser parser,
104             Locale locale, PeriodType type) {
105         super();
106         iPrinter = printer;
107         iParser = parser;
108         iLocale = locale;
109         iParseType = type;
110     }
111 
112     //-----------------------------------------------------------------------
113     /**
114      * Is this formatter capable of printing.
115      * 
116      * @return true if this is a printer
117      */
118     public boolean isPrinter() {
119         return (iPrinter != null);
120     }
121 
122     /**
123      * Gets the internal printer object that performs the real printing work.
124      * 
125      * @return the internal printer
126      */
127     public PeriodPrinter getPrinter() {
128         return iPrinter;
129     }
130 
131     /**
132      * Is this formatter capable of parsing.
133      * 
134      * @return true if this is a parser
135      */
136     public boolean isParser() {
137         return (iParser != null);
138     }
139 
140     /**
141      * Gets the internal parser object that performs the real parsing work.
142      * 
143      * @return the internal parser
144      */
145     public PeriodParser getParser() {
146         return iParser;
147     }
148 
149     //-----------------------------------------------------------------------
150     /**
151      * Returns a new formatter with a different locale that will be used
152      * for printing and parsing.
153      * <p>
154      * A PeriodFormatter is immutable, so a new instance is returned,
155      * and the original is unaltered and still usable.
156      * 
157      * @param locale  the locale to use
158      * @return the new formatter
159      */
160     public PeriodFormatter withLocale(Locale locale) {
161         if (locale == getLocale() || (locale != null && locale.equals(getLocale()))) {
162             return this;
163         }
164         return new PeriodFormatter(iPrinter, iParser, locale, iParseType);
165     }
166 
167     /**
168      * Gets the locale that will be used for printing and parsing.
169      * 
170      * @return the locale to use
171      */
172     public Locale getLocale() {
173         return iLocale;
174     }
175 
176     //-----------------------------------------------------------------------
177     /**
178      * Returns a new formatter with a different PeriodType for parsing.
179      * <p>
180      * A PeriodFormatter is immutable, so a new instance is returned,
181      * and the original is unaltered and still usable.
182      * 
183      * @param type  the type to use in parsing
184      * @return the new formatter
185      */
186     public PeriodFormatter withParseType(PeriodType type) {
187         if (type == iParseType) {
188             return this;
189         }
190         return new PeriodFormatter(iPrinter, iParser, iLocale, type);
191     }
192 
193     /**
194      * Gets the PeriodType that will be used for parsing.
195      * 
196      * @return the parse type to use
197      */
198     public PeriodType getParseType() {
199         return iParseType;
200     }
201 
202     //-----------------------------------------------------------------------
203     /**
204      * Prints a ReadablePeriod to a StringBuffer.
205      *
206      * @param buf  the formatted period is appended to this buffer
207      * @param period  the period to format, not null
208      */
209     public void printTo(StringBuffer buf, ReadablePeriod period) {
210         checkPrinter();
211         checkPeriod(period);
212         
213         getPrinter().printTo(buf, period, iLocale);
214     }
215 
216     /**
217      * Prints a ReadablePeriod to a Writer.
218      *
219      * @param out  the formatted period is written out
220      * @param period  the period to format, not null
221      */
222     public void printTo(Writer out, ReadablePeriod period) throws IOException {
223         checkPrinter();
224         checkPeriod(period);
225         
226         getPrinter().printTo(out, period, iLocale);
227     }
228 
229     /**
230      * Prints a ReadablePeriod to a new String.
231      *
232      * @param period  the period to format, not null
233      * @return the printed result
234      */
235     public String print(ReadablePeriod period) {
236         checkPrinter();
237         checkPeriod(period);
238         
239         PeriodPrinter printer = getPrinter();
240         StringBuffer buf = new StringBuffer(printer.calculatePrintedLength(period, iLocale));
241         printer.printTo(buf, period, iLocale);
242         return buf.toString();
243     }
244 
245     /**
246      * Checks whether printing is supported.
247      * 
248      * @throws UnsupportedOperationException if printing is not supported
249      */
250     private void checkPrinter() {
251         if (iPrinter == null) {
252             throw new UnsupportedOperationException("Printing not supported");
253         }
254     }
255 
256     /**
257      * Checks whether the period is non-null.
258      * 
259      * @throws IllegalArgumentException if the period is null
260      */
261     private void checkPeriod(ReadablePeriod period) {
262         if (period == null) {
263             throw new IllegalArgumentException("Period must not be null");
264         }
265     }
266 
267     //-----------------------------------------------------------------------
268     /**
269      * Parses a period from the given text, at the given position, saving the
270      * result into the fields of the given ReadWritablePeriod. If the parse
271      * succeeds, the return value is the new text position. Note that the parse
272      * may succeed without fully reading the text.
273      * <p>
274      * The parse type of the formatter is not used by this method.
275      * <p>
276      * If it fails, the return value is negative, but the period may still be
277      * modified. To determine the position where the parse failed, apply the
278      * one's complement operator (~) on the return value.
279      *
280      * @param period  a period that will be modified
281      * @param text  text to parse
282      * @param position position to start parsing from
283      * @return new position, if negative, parse failed. Apply complement
284      * operator (~) to get position of failure
285      * @throws IllegalArgumentException if any field is out of range
286      */
287     public int parseInto(ReadWritablePeriod period, String text, int position) {
288         checkParser();
289         checkPeriod(period);
290         
291         return getParser().parseInto(period, text, position, iLocale);
292     }
293 
294     /**
295      * Parses a period from the given text, returning a new Period.
296      *
297      * @param text  text to parse
298      * @return parsed value in a Period object
299      * @throws IllegalArgumentException if any field is out of range
300      */
301     public Period parsePeriod(String text) {
302         checkParser();
303         
304         return parseMutablePeriod(text).toPeriod();
305     }
306 
307     /**
308      * Parses a period from the given text, returning a new MutablePeriod.
309      *
310      * @param text  text to parse
311      * @return parsed value in a MutablePeriod object
312      * @throws IllegalArgumentException if any field is out of range
313      */
314     public MutablePeriod parseMutablePeriod(String text) {
315         checkParser();
316         
317         MutablePeriod period = new MutablePeriod(0, iParseType);
318         int newPos = getParser().parseInto(period, text, 0, iLocale);
319         if (newPos >= 0) {
320             if (newPos >= text.length()) {
321                 return period;
322             }
323         } else {
324             newPos = ~newPos;
325         }
326         throw new IllegalArgumentException(FormatUtils.createErrorMessage(text, newPos));
327     }
328 
329     /**
330      * Checks whether parsing is supported.
331      * 
332      * @throws UnsupportedOperationException if parsing is not supported
333      */
334     private void checkParser() {
335         if (iParser == null) {
336             throw new UnsupportedOperationException("Parsing not supported");
337         }
338     }
339 
340 }