001: /*
002: * Copyright 2001-2006 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.convert;
017:
018: import org.joda.time.Chronology;
019: import org.joda.time.DateTime;
020: import org.joda.time.Period;
021: import org.joda.time.ReadWritableInterval;
022: import org.joda.time.ReadWritablePeriod;
023: import org.joda.time.ReadablePartial;
024: import org.joda.time.field.FieldUtils;
025: import org.joda.time.format.DateTimeFormatter;
026: import org.joda.time.format.ISODateTimeFormat;
027: import org.joda.time.format.ISOPeriodFormat;
028: import org.joda.time.format.PeriodFormatter;
029:
030: /**
031: * StringConverter converts from a String to an instant, partial,
032: * duration, period or interval..
033: *
034: * @author Stephen Colebourne
035: * @author Brian S O'Neill
036: * @since 1.0
037: */
038: class StringConverter extends AbstractConverter implements
039: InstantConverter, PartialConverter, DurationConverter,
040: PeriodConverter, IntervalConverter {
041:
042: /**
043: * Singleton instance.
044: */
045: static final StringConverter INSTANCE = new StringConverter();
046:
047: /**
048: * Restricted constructor.
049: */
050: protected StringConverter() {
051: super ();
052: }
053:
054: //-----------------------------------------------------------------------
055: /**
056: * Gets the millis, which is the ISO parsed string value.
057: *
058: * @param object the String to convert, must not be null
059: * @param chrono the chronology to use, non-null result of getChronology
060: * @return the millisecond value
061: * @throws IllegalArgumentException if the value if invalid
062: */
063: public long getInstantMillis(Object object, Chronology chrono) {
064: String str = (String) object;
065: DateTimeFormatter p = ISODateTimeFormat.dateTimeParser();
066: return p.withChronology(chrono).parseMillis(str);
067: }
068:
069: /**
070: * Extracts the values of the partial from an object of this converter's type.
071: * This method checks if the parser has a zone, and uses it if present.
072: * This is most useful for parsing local times with UTC.
073: *
074: * @param fieldSource a partial that provides access to the fields.
075: * This partial may be incomplete and only getFieldType(int) should be used
076: * @param object the object to convert
077: * @param chrono the chronology to use, which is the non-null result of getChronology()
078: * @param parser the parser to use, may be null
079: * @return the array of field values that match the fieldSource, must be non-null valid
080: * @throws ClassCastException if the object is invalid
081: * @throws IllegalArgumentException if the value if invalid
082: * @since 1.3
083: */
084: public int[] getPartialValues(ReadablePartial fieldSource,
085: Object object, Chronology chrono, DateTimeFormatter parser) {
086: if (parser.getZone() != null) {
087: chrono = chrono.withZone(parser.getZone());
088: }
089: long millis = parser.withChronology(chrono).parseMillis(
090: (String) object);
091: return chrono.get(fieldSource, millis);
092: }
093:
094: //-----------------------------------------------------------------------
095: /**
096: * Gets the duration of the string using the standard type.
097: * This matches the toString() method of ReadableDuration.
098: *
099: * @param object the String to convert, must not be null
100: * @throws ClassCastException if the object is invalid
101: */
102: public long getDurationMillis(Object object) {
103: // parse here because duration could be bigger than the int supported
104: // by the period parser
105: String original = (String) object;
106: String str = original;
107: int len = str.length();
108: if (len >= 4
109: && (str.charAt(0) == 'P' || str.charAt(0) == 'p')
110: && (str.charAt(1) == 'T' || str.charAt(1) == 't')
111: && (str.charAt(len - 1) == 'S' || str.charAt(len - 1) == 's')) {
112: // ok
113: } else {
114: throw new IllegalArgumentException("Invalid format: \""
115: + original + '"');
116: }
117: str = str.substring(2, len - 1);
118: int dot = -1;
119: for (int i = 0; i < str.length(); i++) {
120: if ((str.charAt(i) >= '0' && str.charAt(i) <= '9')
121: || (i == 0 && str.charAt(0) == '-')) {
122: // ok
123: } else if (i > 0 && str.charAt(i) == '.' && dot == -1) {
124: // ok
125: dot = i;
126: } else {
127: throw new IllegalArgumentException("Invalid format: \""
128: + original + '"');
129: }
130: }
131: long millis = 0, seconds = 0;
132: if (dot > 0) {
133: seconds = Long.parseLong(str.substring(0, dot));
134: str = str.substring(dot + 1);
135: if (str.length() != 3) {
136: str = (str + "000").substring(0, 3);
137: }
138: millis = Integer.parseInt(str);
139: } else {
140: seconds = Long.parseLong(str);
141: }
142: if (seconds < 0) {
143: return FieldUtils.safeAdd(FieldUtils.safeMultiply(seconds,
144: 1000), -millis);
145: } else {
146: return FieldUtils.safeAdd(FieldUtils.safeMultiply(seconds,
147: 1000), millis);
148: }
149: }
150:
151: //-----------------------------------------------------------------------
152: /**
153: * Extracts duration values from an object of this converter's type, and
154: * sets them into the given ReadWritableDuration.
155: *
156: * @param period period to get modified
157: * @param object the String to convert, must not be null
158: * @param chrono the chronology to use
159: * @return the millisecond duration
160: * @throws ClassCastException if the object is invalid
161: */
162: public void setInto(ReadWritablePeriod period, Object object,
163: Chronology chrono) {
164: String str = (String) object;
165: PeriodFormatter parser = ISOPeriodFormat.standard();
166: period.clear();
167: int pos = parser.parseInto(period, str, 0);
168: if (pos < str.length()) {
169: if (pos < 0) {
170: // Parse again to get a better exception thrown.
171: parser.withParseType(period.getPeriodType())
172: .parseMutablePeriod(str);
173: }
174: throw new IllegalArgumentException("Invalid format: \""
175: + str + '"');
176: }
177: }
178:
179: //-----------------------------------------------------------------------
180: /**
181: * Sets the value of the mutable interval from the string.
182: *
183: * @param writableInterval the interval to set
184: * @param object the String to convert, must not be null
185: * @param chrono the chronology to use, may be null
186: */
187: public void setInto(ReadWritableInterval writableInterval,
188: Object object, Chronology chrono) {
189: String str = (String) object;
190:
191: int separator = str.indexOf('/');
192: if (separator < 0) {
193: throw new IllegalArgumentException(
194: "Format requires a '/' separator: " + str);
195: }
196:
197: String leftStr = str.substring(0, separator);
198: if (leftStr.length() <= 0) {
199: throw new IllegalArgumentException("Format invalid: " + str);
200: }
201: String rightStr = str.substring(separator + 1);
202: if (rightStr.length() <= 0) {
203: throw new IllegalArgumentException("Format invalid: " + str);
204: }
205:
206: DateTimeFormatter dateTimeParser = ISODateTimeFormat
207: .dateTimeParser();
208: dateTimeParser = dateTimeParser.withChronology(chrono);
209: PeriodFormatter periodParser = ISOPeriodFormat.standard();
210: long startInstant = 0, endInstant = 0;
211: Period period = null;
212: Chronology parsedChrono = null;
213:
214: // before slash
215: char c = leftStr.charAt(0);
216: if (c == 'P' || c == 'p') {
217: period = periodParser.withParseType(getPeriodType(leftStr))
218: .parsePeriod(leftStr);
219: } else {
220: DateTime start = dateTimeParser.parseDateTime(leftStr);
221: startInstant = start.getMillis();
222: parsedChrono = start.getChronology();
223: }
224:
225: // after slash
226: c = rightStr.charAt(0);
227: if (c == 'P' || c == 'p') {
228: if (period != null) {
229: throw new IllegalArgumentException(
230: "Interval composed of two durations: " + str);
231: }
232: period = periodParser
233: .withParseType(getPeriodType(rightStr))
234: .parsePeriod(rightStr);
235: chrono = (chrono != null ? chrono : parsedChrono);
236: endInstant = chrono.add(period, startInstant, 1);
237: } else {
238: DateTime end = dateTimeParser.parseDateTime(rightStr);
239: endInstant = end.getMillis();
240: parsedChrono = (parsedChrono != null ? parsedChrono : end
241: .getChronology());
242: chrono = (chrono != null ? chrono : parsedChrono);
243: if (period != null) {
244: startInstant = chrono.add(period, endInstant, -1);
245: }
246: }
247:
248: writableInterval.setInterval(startInstant, endInstant);
249: writableInterval.setChronology(chrono);
250: }
251:
252: //-----------------------------------------------------------------------
253: /**
254: * Returns String.class.
255: *
256: * @return String.class
257: */
258: public Class getSupportedType() {
259: return String.class;
260: }
261:
262: }
|