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 }