001 /*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements. See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License. You may obtain a copy of the License at
008 *
009 * http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017
018 package org.apache.commons.net.ftp.parser;
019
020 import java.text.DateFormatSymbols;
021 import java.text.ParseException;
022 import java.text.ParsePosition;
023 import java.text.SimpleDateFormat;
024 import java.util.Calendar;
025 import java.util.Date;
026 import java.util.TimeZone;
027
028 import org.apache.commons.net.ftp.Configurable;
029 import org.apache.commons.net.ftp.FTPClientConfig;
030
031 /**
032 * Default implementation of the {@link FTPTimestampParser FTPTimestampParser}
033 * interface also implements the {@link org.apache.commons.net.ftp.Configurable Configurable}
034 * interface to allow the parsing to be configured from the outside.
035 *
036 * @see ConfigurableFTPFileEntryParserImpl
037 * @since 1.4
038 */
039 public class FTPTimestampParserImpl implements
040 FTPTimestampParser, Configurable
041 {
042
043
044 private SimpleDateFormat defaultDateFormat;
045 private SimpleDateFormat recentDateFormat;
046 private boolean lenientFutureDates = false;
047
048
049 /**
050 * The only constructor for this class.
051 */
052 public FTPTimestampParserImpl() {
053 setDefaultDateFormat(DEFAULT_SDF);
054 setRecentDateFormat(DEFAULT_RECENT_SDF);
055 }
056
057 /**
058 * Implements the one {@link FTPTimestampParser#parseTimestamp(String) method}
059 * in the {@link FTPTimestampParser FTPTimestampParser} interface
060 * according to this algorithm:
061 *
062 * If the recentDateFormat member has been defined, try to parse the
063 * supplied string with that. If that parse fails, or if the recentDateFormat
064 * member has not been defined, attempt to parse with the defaultDateFormat
065 * member. If that fails, throw a ParseException.
066 *
067 * This method allows a {@link Calendar} instance to be passed in which represents the
068 * current (system) time.
069 *
070 * @see org.apache.commons.net.ftp.parser.FTPTimestampParser#parseTimestamp(java.lang.String)
071 *
072 * @param timestampStr The timestamp to be parsed
073 */
074 public Calendar parseTimestamp(String timestampStr) throws ParseException {
075 Calendar now = Calendar.getInstance();
076 return parseTimestamp(timestampStr, now);
077 }
078
079 /**
080 * Implements the one {@link FTPTimestampParser#parseTimestamp(String) method}
081 * in the {@link FTPTimestampParser FTPTimestampParser} interface
082 * according to this algorithm:
083 *
084 * If the recentDateFormat member has been defined, try to parse the
085 * supplied string with that. If that parse fails, or if the recentDateFormat
086 * member has not been defined, attempt to parse with the defaultDateFormat
087 * member. If that fails, throw a ParseException.
088 *
089 * @see org.apache.commons.net.ftp.parser.FTPTimestampParser#parseTimestamp(java.lang.String)
090 * @param timestampStr The timestamp to be parsed
091 * @param serverTime The current time for the server
092 * @since 1.5
093 */
094 public Calendar parseTimestamp(String timestampStr, Calendar serverTime) throws ParseException {
095 Calendar now = (Calendar) serverTime.clone();// Copy this, because we may change it
096 now.setTimeZone(this.getServerTimeZone());
097 Calendar working = (Calendar) now.clone();
098 working.setTimeZone(getServerTimeZone());
099 ParsePosition pp = new ParsePosition(0);
100
101 Date parsed = null;
102 if (recentDateFormat != null) {
103 if (lenientFutureDates) {
104 // add a day to "now" so that "slop" doesn't cause a date
105 // slightly in the future to roll back a full year. (Bug 35181)
106 now.add(Calendar.DATE, 1);
107 }
108 parsed = recentDateFormat.parse(timestampStr, pp);
109 }
110 if (parsed != null && pp.getIndex() == timestampStr.length())
111 {
112 working.setTime(parsed);
113 working.set(Calendar.YEAR, now.get(Calendar.YEAR));
114
115 if (working.after(now)) {
116 working.add(Calendar.YEAR, -1);
117 }
118 } else {
119 // Temporarily add the current year to the short date time
120 // to cope with short-date leap year strings.
121 // e.g. Java's DateFormatter will assume that "Feb 29 12:00" refers to
122 // Feb 29 1970 (an invalid date) rather than a potentially valid leap year date.
123 // This is pretty bad hack to work around the deficiencies of the JDK date/time classes.
124 if (recentDateFormat != null) {
125 pp = new ParsePosition(0);
126 int year = now.get(Calendar.YEAR);
127 String timeStampStrPlusYear = timestampStr + " " + year;
128 SimpleDateFormat hackFormatter = new SimpleDateFormat(recentDateFormat.toPattern() + " yyyy",
129 recentDateFormat.getDateFormatSymbols());
130 hackFormatter.setLenient(false);
131 hackFormatter.setTimeZone(recentDateFormat.getTimeZone());
132 parsed = hackFormatter.parse(timeStampStrPlusYear, pp);
133 }
134 if (parsed != null && pp.getIndex() == timestampStr.length() + 5) {
135 working.setTime(parsed);
136 }
137 else {
138 pp = new ParsePosition(0);
139 parsed = defaultDateFormat.parse(timestampStr, pp);
140 // note, length checks are mandatory for us since
141 // SimpleDateFormat methods will succeed if less than
142 // full string is matched. They will also accept,
143 // despite "leniency" setting, a two-digit number as
144 // a valid year (e.g. 22:04 will parse as 22 A.D.)
145 // so could mistakenly confuse an hour with a year,
146 // if we don't insist on full length parsing.
147 if (parsed != null && pp.getIndex() == timestampStr.length()) {
148 working.setTime(parsed);
149 } else {
150 throw new ParseException(
151 "Timestamp could not be parsed with older or recent DateFormat",
152 pp.getIndex());
153 }
154 }
155 }
156 return working;
157 }
158
159 /**
160 * @return Returns the defaultDateFormat.
161 */
162 public SimpleDateFormat getDefaultDateFormat() {
163 return defaultDateFormat;
164 }
165 /**
166 * @return Returns the defaultDateFormat pattern string.
167 */
168 public String getDefaultDateFormatString() {
169 return defaultDateFormat.toPattern();
170 }
171 /**
172 * @param defaultDateFormat The defaultDateFormat to be set.
173 */
174 private void setDefaultDateFormat(String format) {
175 if (format != null) {
176 this.defaultDateFormat = new SimpleDateFormat(format);
177 this.defaultDateFormat.setLenient(false);
178 }
179 }
180 /**
181 * @return Returns the recentDateFormat.
182 */
183 public SimpleDateFormat getRecentDateFormat() {
184 return recentDateFormat;
185 }
186 /**
187 * @return Returns the recentDateFormat.
188 */
189 public String getRecentDateFormatString() {
190 return recentDateFormat.toPattern();
191 }
192 /**
193 * @param recentDateFormat The recentDateFormat to set.
194 */
195 private void setRecentDateFormat(String format) {
196 if (format != null) {
197 this.recentDateFormat = new SimpleDateFormat(format);
198 this.recentDateFormat.setLenient(false);
199 }
200 }
201
202 /**
203 * @return returns an array of 12 strings representing the short
204 * month names used by this parse.
205 */
206 public String[] getShortMonths() {
207 return defaultDateFormat.getDateFormatSymbols().getShortMonths();
208 }
209
210
211 /**
212 * @return Returns the serverTimeZone used by this parser.
213 */
214 public TimeZone getServerTimeZone() {
215 return this.defaultDateFormat.getTimeZone();
216 }
217 /**
218 * sets a TimeZone represented by the supplied ID string into all
219 * of the parsers used by this server.
220 * @param serverTimeZone Time Id java.util.TimeZone id used by
221 * the ftp server. If null the client's local time zone is assumed.
222 */
223 private void setServerTimeZone(String serverTimeZoneId) {
224 TimeZone serverTimeZone = TimeZone.getDefault();
225 if (serverTimeZoneId != null) {
226 serverTimeZone = TimeZone.getTimeZone(serverTimeZoneId);
227 }
228 this.defaultDateFormat.setTimeZone(serverTimeZone);
229 if (this.recentDateFormat != null) {
230 this.recentDateFormat.setTimeZone(serverTimeZone);
231 }
232 }
233
234 /**
235 * Implementation of the {@link Configurable Configurable}
236 * interface. Configures this <code>FTPTimestampParser</code> according
237 * to the following logic:
238 * <p>
239 * Set up the {@link FTPClientConfig#setDefaultDateFormatStr(java.lang.String) defaultDateFormat}
240 * and optionally the {@link FTPClientConfig#setRecentDateFormatStr(String) recentDateFormat}
241 * to values supplied in the config based on month names configured as follows:
242 * </p><p><ul>
243 * <li>If a {@link FTPClientConfig#setShortMonthNames(String) shortMonthString}
244 * has been supplied in the <code>config</code>, use that to parse parse timestamps.</li>
245 * <li>Otherwise, if a {@link FTPClientConfig#setServerLanguageCode(String) serverLanguageCode}
246 * has been supplied in the <code>config</code>, use the month names represented
247 * by that {@link FTPClientConfig#lookupDateFormatSymbols(String) language}
248 * to parse timestamps.</li>
249 * <li>otherwise use default English month names</li>
250 * </ul></p><p>
251 * Finally if a {@link org.apache.commons.net.ftp.FTPClientConfig#setServerTimeZoneId(String) serverTimeZoneId}
252 * has been supplied via the config, set that into all date formats that have
253 * been configured.
254 * </p>
255 */
256 public void configure(FTPClientConfig config) {
257 DateFormatSymbols dfs = null;
258
259 String languageCode = config.getServerLanguageCode();
260 String shortmonths = config.getShortMonthNames();
261 if (shortmonths != null) {
262 dfs = FTPClientConfig.getDateFormatSymbols(shortmonths);
263 } else if (languageCode != null) {
264 dfs = FTPClientConfig.lookupDateFormatSymbols(languageCode);
265 } else {
266 dfs = FTPClientConfig.lookupDateFormatSymbols("en");
267 }
268
269
270 String recentFormatString = config.getRecentDateFormatStr();
271 if (recentFormatString == null) {
272 this.recentDateFormat = null;
273 } else {
274 this.recentDateFormat = new SimpleDateFormat(recentFormatString, dfs);
275 this.recentDateFormat.setLenient(false);
276 }
277
278 String defaultFormatString = config.getDefaultDateFormatStr();
279 if (defaultFormatString == null) {
280 throw new IllegalArgumentException("defaultFormatString cannot be null");
281 }
282 this.defaultDateFormat = new SimpleDateFormat(defaultFormatString, dfs);
283 this.defaultDateFormat.setLenient(false);
284
285 setServerTimeZone(config.getServerTimeZoneId());
286
287 this.lenientFutureDates = config.isLenientFutureDates();
288 }
289 /**
290 * @return Returns the lenientFutureDates.
291 */
292 boolean isLenientFutureDates() {
293 return lenientFutureDates;
294 }
295 /**
296 * @param lenientFutureDates The lenientFutureDates to set.
297 */
298 void setLenientFutureDates(boolean lenientFutureDates) {
299 this.lenientFutureDates = lenientFutureDates;
300 }
301 }