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.format;
017
018 import java.io.IOException;
019 import java.io.Writer;
020 import java.util.Locale;
021
022 import org.joda.time.MutablePeriod;
023 import org.joda.time.Period;
024 import org.joda.time.PeriodType;
025 import org.joda.time.ReadWritablePeriod;
026 import org.joda.time.ReadablePeriod;
027
028 /**
029 * Controls the printing and parsing of a time period to and from a string.
030 * <p>
031 * This class is the main API for printing and parsing used by most applications.
032 * Instances of this class are created via one of three factory classes:
033 * <ul>
034 * <li>{@link PeriodFormat} - formats by pattern and style</li>
035 * <li>{@link ISOPeriodFormat} - ISO8601 formats</li>
036 * <li>{@link PeriodFormatterBuilder} - complex formats created via method calls</li>
037 * </ul>
038 * <p>
039 * An instance of this class holds a reference internally to one printer and
040 * one parser. It is possible that one of these may be null, in which case the
041 * formatter cannot print/parse. This can be checked via the {@link #isPrinter()}
042 * and {@link #isParser()} methods.
043 * <p>
044 * The underlying printer/parser can be altered to behave exactly as required
045 * by using a decorator modifier:
046 * <ul>
047 * <li>{@link #withLocale(Locale)} - returns a new formatter that uses the specified locale</li>
048 * </ul>
049 * This returns a new formatter (instances of this class are immutable).
050 * <p>
051 * The main methods of the class are the <code>printXxx</code> and
052 * <code>parseXxx</code> methods. These are used as follows:
053 * <pre>
054 * // print using the default locale
055 * String periodStr = formatter.print(period);
056 * // print using the French locale
057 * String periodStr = formatter.withLocale(Locale.FRENCH).print(period);
058 *
059 * // parse using the French locale
060 * Period date = formatter.withLocale(Locale.FRENCH).parsePeriod(str);
061 * </pre>
062 *
063 * @author Brian S O'Neill
064 * @author Stephen Colebourne
065 * @since 1.0
066 */
067 public class PeriodFormatter {
068
069 /** The internal printer used to output the datetime. */
070 private final PeriodPrinter iPrinter;
071 /** The internal parser used to output the datetime. */
072 private final PeriodParser iParser;
073 /** The locale to use for printing and parsing. */
074 private final Locale iLocale;
075 /** The period type used in parsing. */
076 private final PeriodType iParseType;
077
078 /**
079 * Creates a new formatter, however you will normally use the factory
080 * or the builder.
081 *
082 * @param printer the internal printer, null if cannot print
083 * @param parser the internal parser, null if cannot parse
084 */
085 public PeriodFormatter(
086 PeriodPrinter printer, PeriodParser parser) {
087 super();
088 iPrinter = printer;
089 iParser = parser;
090 iLocale = null;
091 iParseType = null;
092 }
093
094 /**
095 * Constructor.
096 *
097 * @param printer the internal printer, null if cannot print
098 * @param parser the internal parser, null if cannot parse
099 * @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 }