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 }