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 allows a {@link Calendar} instance to be passed in which represents the
68       * current (system) time.
69       * 
70       * @see org.apache.commons.net.ftp.parser.FTPTimestampParser#parseTimestamp(java.lang.String)
71       * 
72       * @param timestampStr The timestamp to be parsed
73       */
74      public Calendar parseTimestamp(String timestampStr) throws ParseException {
75          Calendar now = Calendar.getInstance();
76          return parseTimestamp(timestampStr, now);
77      }
78      
79      /** 
80       * Implements the one {@link  FTPTimestampParser#parseTimestamp(String)  method}
81       * in the {@link  FTPTimestampParser  FTPTimestampParser} interface 
82       * according to this algorithm:
83       * 
84       * If the recentDateFormat member has been defined, try to parse the 
85       * supplied string with that.  If that parse fails, or if the recentDateFormat
86       * member has not been defined, attempt to parse with the defaultDateFormat
87       * member.  If that fails, throw a ParseException. 
88       * 
89       * @see org.apache.commons.net.ftp.parser.FTPTimestampParser#parseTimestamp(java.lang.String)
90       * @param timestampStr The timestamp to be parsed
91       * @param serverTime The current time for the server
92       * @since 1.5
93       */
94      public Calendar parseTimestamp(String timestampStr, Calendar serverTime) throws ParseException {
95          Calendar now = (Calendar) serverTime.clone();// Copy this, because we may change it
96          now.setTimeZone(this.getServerTimeZone());
97          Calendar working = (Calendar) now.clone();
98          working.setTimeZone(getServerTimeZone());
99          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 }