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(PeriodPrinter printer, PeriodParser parser) {
086: super ();
087: iPrinter = printer;
088: iParser = parser;
089: iLocale = null;
090: iParseType = null;
091: }
092:
093: /**
094: * Constructor.
095: *
096: * @param printer the internal printer, null if cannot print
097: * @param parser the internal parser, null if cannot parse
098: * @param locale the locale to use
099: * @param type the parse period type
100: */
101: private PeriodFormatter(PeriodPrinter printer, PeriodParser parser,
102: Locale locale, PeriodType type) {
103: super ();
104: iPrinter = printer;
105: iParser = parser;
106: iLocale = locale;
107: iParseType = type;
108: }
109:
110: //-----------------------------------------------------------------------
111: /**
112: * Is this formatter capable of printing.
113: *
114: * @return true if this is a printer
115: */
116: public boolean isPrinter() {
117: return (iPrinter != null);
118: }
119:
120: /**
121: * Gets the internal printer object that performs the real printing work.
122: *
123: * @return the internal printer
124: */
125: public PeriodPrinter getPrinter() {
126: return iPrinter;
127: }
128:
129: /**
130: * Is this formatter capable of parsing.
131: *
132: * @return true if this is a parser
133: */
134: public boolean isParser() {
135: return (iParser != null);
136: }
137:
138: /**
139: * Gets the internal parser object that performs the real parsing work.
140: *
141: * @return the internal parser
142: */
143: public PeriodParser getParser() {
144: return iParser;
145: }
146:
147: //-----------------------------------------------------------------------
148: /**
149: * Returns a new formatter with a different locale that will be used
150: * for printing and parsing.
151: * <p>
152: * A PeriodFormatter is immutable, so a new instance is returned,
153: * and the original is unaltered and still usable.
154: *
155: * @param locale the locale to use
156: * @return the new formatter
157: */
158: public PeriodFormatter withLocale(Locale locale) {
159: if (locale == getLocale()
160: || (locale != null && locale.equals(getLocale()))) {
161: return this ;
162: }
163: return new PeriodFormatter(iPrinter, iParser, locale,
164: 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)
223: throws IOException {
224: checkPrinter();
225: checkPeriod(period);
226:
227: getPrinter().printTo(out, period, iLocale);
228: }
229:
230: /**
231: * Prints a ReadablePeriod to a new String.
232: *
233: * @param period the period to format, not null
234: * @return the printed result
235: */
236: public String print(ReadablePeriod period) {
237: checkPrinter();
238: checkPeriod(period);
239:
240: PeriodPrinter printer = getPrinter();
241: StringBuffer buf = new StringBuffer(printer
242: .calculatePrintedLength(period, iLocale));
243: printer.printTo(buf, period, iLocale);
244: return buf.toString();
245: }
246:
247: /**
248: * Checks whether printing is supported.
249: *
250: * @throws UnsupportedOperationException if printing is not supported
251: */
252: private void checkPrinter() {
253: if (iPrinter == null) {
254: throw new UnsupportedOperationException(
255: "Printing not supported");
256: }
257: }
258:
259: /**
260: * Checks whether the period is non-null.
261: *
262: * @throws IllegalArgumentException if the period is null
263: */
264: private void checkPeriod(ReadablePeriod period) {
265: if (period == null) {
266: throw new IllegalArgumentException(
267: "Period must not be null");
268: }
269: }
270:
271: //-----------------------------------------------------------------------
272: /**
273: * Parses a period from the given text, at the given position, saving the
274: * result into the fields of the given ReadWritablePeriod. If the parse
275: * succeeds, the return value is the new text position. Note that the parse
276: * may succeed without fully reading the text.
277: * <p>
278: * The parse type of the formatter is not used by this method.
279: * <p>
280: * If it fails, the return value is negative, but the period may still be
281: * modified. To determine the position where the parse failed, apply the
282: * one's complement operator (~) on the return value.
283: *
284: * @param period a period that will be modified
285: * @param text text to parse
286: * @param position position to start parsing from
287: * @return new position, if negative, parse failed. Apply complement
288: * operator (~) to get position of failure
289: * @throws IllegalArgumentException if any field is out of range
290: */
291: public int parseInto(ReadWritablePeriod period, String text,
292: int position) {
293: checkParser();
294: checkPeriod(period);
295:
296: return getParser().parseInto(period, text, position, iLocale);
297: }
298:
299: /**
300: * Parses a period from the given text, returning a new Period.
301: *
302: * @param text text to parse
303: * @return parsed value in a Period object
304: * @throws IllegalArgumentException if any field is out of range
305: */
306: public Period parsePeriod(String text) {
307: checkParser();
308:
309: return parseMutablePeriod(text).toPeriod();
310: }
311:
312: /**
313: * Parses a period from the given text, returning a new MutablePeriod.
314: *
315: * @param text text to parse
316: * @return parsed value in a MutablePeriod object
317: * @throws IllegalArgumentException if any field is out of range
318: */
319: public MutablePeriod parseMutablePeriod(String text) {
320: checkParser();
321:
322: MutablePeriod period = new MutablePeriod(0, iParseType);
323: int newPos = getParser().parseInto(period, text, 0, iLocale);
324: if (newPos >= 0) {
325: if (newPos >= text.length()) {
326: return period;
327: }
328: } else {
329: newPos = ~newPos;
330: }
331: throw new IllegalArgumentException(FormatUtils
332: .createErrorMessage(text, newPos));
333: }
334:
335: /**
336: * Checks whether parsing is supported.
337: *
338: * @throws UnsupportedOperationException if parsing is not supported
339: */
340: private void checkParser() {
341: if (iParser == null) {
342: throw new UnsupportedOperationException(
343: "Parsing not supported");
344: }
345: }
346:
347: }
|