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 }