View Javadoc

1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  
18  package org.apache.commons.net.ftp.parser;
19  
20  import java.text.DateFormatSymbols;
21  import java.text.ParseException;
22  import java.text.ParsePosition;
23  import java.text.SimpleDateFormat;
24  import java.util.Calendar;
25  import java.util.Date;
26  import java.util.TimeZone;
27  
28  import org.apache.commons.net.ftp.Configurable;
29  import org.apache.commons.net.ftp.FTPClientConfig;
30  
31  /**
32   * Default implementation of the {@link  FTPTimestampParser  FTPTimestampParser}
33   * interface also implements the {@link  org.apache.commons.net.ftp.Configurable  Configurable}
34   * interface to allow the parsing to be configured from the outside.
35   *
36   * @see ConfigurableFTPFileEntryParserImpl
37   * @since 1.4
38   */
39  public class FTPTimestampParserImpl implements
40          FTPTimestampParser, Configurable
41  {
42  
43  
44      private SimpleDateFormat defaultDateFormat;
45      private SimpleDateFormat recentDateFormat;
46      private boolean lenientFutureDates = false;
47  
48  
49      /**
50       * The only constructor for this class.
51       */
52      public FTPTimestampParserImpl() {
53          setDefaultDateFormat(DEFAULT_SDF);
54          setRecentDateFormat(DEFAULT_RECENT_SDF);
55      }
56  
57      /**
58       * Implements the one {@link  FTPTimestampParser#parseTimestamp(String)  method}
59       * in the {@link  FTPTimestampParser  FTPTimestampParser} interface
60       * according to this algorithm:
61       *
62       * If the recentDateFormat member has been defined, try to parse the
63       * supplied string with that.  If that parse fails, or if the recentDateFormat
64       * member has not been defined, attempt to parse with the defaultDateFormat
65       * member.  If that fails, throw a ParseException.
66       *
67       * This method assumes that the server time is the same as the local time.
68       *
69       * @see FTPTimestampParserImpl#parseTimestamp(String, Calendar)
70       *
71       * @param timestampStr The timestamp to be parsed
72       */
73  //    @Override
74      public Calendar parseTimestamp(String timestampStr) throws ParseException {
75          Calendar now = Calendar.getInstance();
76          return parseTimestamp(timestampStr, now);
77      }
78  
79      /**
80       * If the recentDateFormat member has been defined, try to parse the
81       * supplied string with that.  If that parse fails, or if the recentDateFormat
82       * member has not been defined, attempt to parse with the defaultDateFormat
83       * member.  If that fails, throw a ParseException.
84       *
85       * This method allows a {@link Calendar} instance to be passed in which represents the
86       * current (system) time.
87       *
88       * @see FTPTimestampParser#parseTimestamp(String)
89       * @param timestampStr The timestamp to be parsed
90       * @param serverTime The current time for the server
91       * @since 1.5
92       */
93      public Calendar parseTimestamp(String timestampStr, Calendar serverTime) throws ParseException {
94          Calendar working = (Calendar) serverTime.clone();
95          working.setTimeZone(getServerTimeZone()); // is this needed?
96  
97          Date parsed = null;
98  
99          if (recentDateFormat != null) {
100             Calendar now = (Calendar) serverTime.clone();// Copy this, because we may change it
101             now.setTimeZone(this.getServerTimeZone());
102             if (lenientFutureDates) {
103                 // add a day to "now" so that "slop" doesn't cause a date
104                 // slightly in the future to roll back a full year.  (Bug 35181 => NET-83)
105                 now.add(Calendar.DATE, 1);
106             }
107             // The Java SimpleDateFormat class uses the epoch year 1970 if not present in the input
108             // As 1970 was not a leap year, it cannot parse "Feb 29" correctly.
109             // Java 1.5+ returns Mar 1 1970
110             // Temporarily add the current year to the short date time
111             // to cope with short-date leap year strings.
112             // Since Feb 29 is more that 6 months from the end of the year, this should be OK for
113             // all instances of short dates which are +- 6 months from current date.
114             // TODO this won't always work for systems that use short dates +0/-12months
115             // e.g. if today is Jan 1 2001 and the short date is Feb 29
116             String year = Integer.toString(now.get(Calendar.YEAR));
117             String timeStampStrPlusYear = timestampStr + " " + year;
118             SimpleDateFormat hackFormatter = new SimpleDateFormat(recentDateFormat.toPattern() + " yyyy",
119                     recentDateFormat.getDateFormatSymbols());
120             hackFormatter.setLenient(false);
121             hackFormatter.setTimeZone(recentDateFormat.getTimeZone());
122             ParsePosition pp = new ParsePosition(0);
123             parsed = hackFormatter.parse(timeStampStrPlusYear, pp);
124             // Check if we parsed the full string, if so it must have been a short date originally
125             if (parsed != null && pp.getIndex() == timeStampStrPlusYear.length()) {
126                 working.setTime(parsed);
127                 if (working.after(now)) { // must have been last year instead
128                     working.add(Calendar.YEAR, -1);
129                 }
130                 return working;
131             }
132         }
133 
134         ParsePosition pp = new ParsePosition(0);
135         parsed = defaultDateFormat.parse(timestampStr, pp);
136         // note, length checks are mandatory for us since
137         // SimpleDateFormat methods will succeed if less than
138         // full string is matched.  They will also accept,
139         // despite "leniency" setting, a two-digit number as
140         // a valid year (e.g. 22:04 will parse as 22 A.D.)
141         // so could mistakenly confuse an hour with a year,
142         // if we don't insist on full length parsing.
143         if (parsed != null && pp.getIndex() == timestampStr.length()) {
144             working.setTime(parsed);
145         } else {
146             throw new ParseException(
147                     "Timestamp '"+timestampStr+"' could not be parsed using a server time of "
148                         +serverTime.getTime().toString(),
149                     pp.getErrorIndex());
150         }
151         return working;
152     }
153 
154     /**
155      * @return Returns the defaultDateFormat.
156      */
157     public SimpleDateFormat getDefaultDateFormat() {
158         return defaultDateFormat;
159     }
160     /**
161      * @return Returns the defaultDateFormat pattern string.
162      */
163     public String getDefaultDateFormatString() {
164         return defaultDateFormat.toPattern();
165     }
166     /**
167      * @param defaultDateFormat The defaultDateFormat to be set.
168      */
169     private void setDefaultDateFormat(String format) {
170         if (format != null) {
171             this.defaultDateFormat = new SimpleDateFormat(format);
172             this.defaultDateFormat.setLenient(false);
173         }
174     }
175     /**
176      * @return Returns the recentDateFormat.
177      */
178     public SimpleDateFormat getRecentDateFormat() {
179         return recentDateFormat;
180     }
181     /**
182      * @return Returns the recentDateFormat.
183      */
184     public String getRecentDateFormatString() {
185         return recentDateFormat.toPattern();
186     }
187     /**
188      * @param recentDateFormat The recentDateFormat to set.
189      */
190     private void setRecentDateFormat(String format) {
191         if (format != null) {
192             this.recentDateFormat = new SimpleDateFormat(format);
193             this.recentDateFormat.setLenient(false);
194         }
195     }
196 
197     /**
198      * @return returns an array of 12 strings representing the short
199      * month names used by this parse.
200      */
201     public String[] getShortMonths() {
202         return defaultDateFormat.getDateFormatSymbols().getShortMonths();
203     }
204 
205 
206     /**
207      * @return Returns the serverTimeZone used by this parser.
208      */
209     public TimeZone getServerTimeZone() {
210         return this.defaultDateFormat.getTimeZone();
211     }
212     /**
213      * sets a TimeZone represented by the supplied ID string into all
214      * of the parsers used by this server.
215      * @param serverTimeZone Time Id java.util.TimeZone id used by
216      * the ftp server.  If null the client's local time zone is assumed.
217      */
218     private void setServerTimeZone(String serverTimeZoneId) {
219         TimeZone serverTimeZone = TimeZone.getDefault();
220         if (serverTimeZoneId != null) {
221             serverTimeZone = TimeZone.getTimeZone(serverTimeZoneId);
222         }
223         this.defaultDateFormat.setTimeZone(serverTimeZone);
224         if (this.recentDateFormat != null) {
225             this.recentDateFormat.setTimeZone(serverTimeZone);
226         }
227     }
228 
229     /**
230      * Implementation of the {@link  Configurable  Configurable}
231      * interface. Configures this <code>FTPTimestampParser</code> according
232      * to the following logic:
233      * <p>
234      * Set up the {@link  FTPClientConfig#setDefaultDateFormatStr(java.lang.String) defaultDateFormat}
235      * and optionally the {@link  FTPClientConfig#setRecentDateFormatStr(String) recentDateFormat}
236      * to values supplied in the config based on month names configured as follows:
237      * </p><p><ul>
238      * <li>If a {@link  FTPClientConfig#setShortMonthNames(String) shortMonthString}
239      * has been supplied in the <code>config</code>, use that to parse  parse timestamps.</li>
240      * <li>Otherwise, if a {@link  FTPClientConfig#setServerLanguageCode(String) serverLanguageCode}
241      * has been supplied in the <code>config</code>, use the month names represented
242      * by that {@link  FTPClientConfig#lookupDateFormatSymbols(String) language}
243      * to parse timestamps.</li>
244      * <li>otherwise use default English month names</li>
245      * </ul></p><p>
246      * Finally if a {@link  org.apache.commons.net.ftp.FTPClientConfig#setServerTimeZoneId(String) serverTimeZoneId}
247      * has been supplied via the config, set that into all date formats that have
248      * been configured.
249      * </p>
250      */
251 //    @Override
252     public void configure(FTPClientConfig config) {
253         DateFormatSymbols dfs = null;
254 
255         String languageCode = config.getServerLanguageCode();
256         String shortmonths = config.getShortMonthNames();
257         if (shortmonths != null) {
258             dfs = FTPClientConfig.getDateFormatSymbols(shortmonths);
259         } else if (languageCode != null) {
260             dfs = FTPClientConfig.lookupDateFormatSymbols(languageCode);
261         } else {
262             dfs = FTPClientConfig.lookupDateFormatSymbols("en");
263         }
264 
265 
266         String recentFormatString = config.getRecentDateFormatStr();
267         if (recentFormatString == null) {
268             this.recentDateFormat = null;
269         } else {
270             this.recentDateFormat = new SimpleDateFormat(recentFormatString, dfs);
271             this.recentDateFormat.setLenient(false);
272         }
273 
274         String defaultFormatString = config.getDefaultDateFormatStr();
275         if (defaultFormatString == null) {
276             throw new IllegalArgumentException("defaultFormatString cannot be null");
277         }
278         this.defaultDateFormat = new SimpleDateFormat(defaultFormatString, dfs);
279         this.defaultDateFormat.setLenient(false);
280 
281         setServerTimeZone(config.getServerTimeZoneId());
282 
283         this.lenientFutureDates = config.isLenientFutureDates();
284     }
285     /**
286      * @return Returns the lenientFutureDates.
287      */
288     boolean isLenientFutureDates() {
289         return lenientFutureDates;
290     }
291     /**
292      * @param lenientFutureDates The lenientFutureDates to set.
293      */
294     void setLenientFutureDates(boolean lenientFutureDates) {
295         this.lenientFutureDates = lenientFutureDates;
296     }
297 }