001    /*
002     * Copyright 2005 The Apache Software Foundation
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.apache.commons.net.ftp.parser;
017    
018    import java.text.DateFormatSymbols;
019    import java.text.ParseException;
020    import java.text.ParsePosition;
021    import java.text.SimpleDateFormat;
022    import java.util.Calendar;
023    import java.util.Date;
024    import java.util.TimeZone;
025    
026    import org.apache.commons.net.ftp.Configurable;
027    import org.apache.commons.net.ftp.FTPClientConfig;
028    
029    /**
030     * Default implementation of the {@link  FTPTimestampParser  FTPTimestampParser} 
031     * interface also implements the {@link  org.apache.commons.net.ftp.Configurable  Configurable}
032     * interface to allow the parsing to be configured from the outside.
033     *
034     * @see ConfigurableFTPFileEntryParserImpl
035     * @since 1.4
036     */
037    public class FTPTimestampParserImpl implements
038                    FTPTimestampParser, Configurable 
039    {
040    
041            
042            private SimpleDateFormat defaultDateFormat;
043            private SimpleDateFormat recentDateFormat;
044            
045            
046            /**
047             * The only constructor for this class. 
048             */
049            public FTPTimestampParserImpl() {
050                    setDefaultDateFormat(DEFAULT_SDF);
051                    setRecentDateFormat(DEFAULT_RECENT_SDF);
052            }
053            
054            /** 
055             * Implements the one {@link  FTPTimestampParser#parseTimestamp(String)  method}
056             * in the {@link  FTPTimestampParser  FTPTimestampParser} interface 
057             * according to this algorithm:
058             * 
059             * If the recentDateFormat member has been defined, try to parse the 
060             * supplied string with that.  If that parse fails, or if the recentDateFormat
061             * member has not been defined, attempt to parse with the defaultDateFormat
062             * member.  If that fails, throw a ParseException. 
063             * 
064             * @see org.apache.commons.net.ftp.parser.FTPTimestampParser#parseTimestamp(java.lang.String)    
065             */
066            /* (non-Javadoc)
067             * 
068             */
069            public Calendar parseTimestamp(String timestampStr) throws ParseException {
070                    Calendar now = Calendar.getInstance();
071                    now.setTimeZone(this.getServerTimeZone());
072                    
073                    Calendar working = Calendar.getInstance();
074                    working.setTimeZone(this.getServerTimeZone());
075                    ParsePosition pp = new ParsePosition(0);
076    
077                    Date parsed = null;
078                    if (this.recentDateFormat != null) {
079                            parsed = recentDateFormat.parse(timestampStr, pp);
080                    }
081                    if (parsed != null && pp.getIndex() == timestampStr.length()) 
082                    {
083                            working.setTime(parsed);
084                            working.set(Calendar.YEAR, now.get(Calendar.YEAR));
085                            if (working.after(now)) {
086                                    working.add(Calendar.YEAR, -1);
087                            }
088                    } else {
089                            pp = new ParsePosition(0);
090                            parsed = defaultDateFormat.parse(timestampStr, pp);
091                            // note, length checks are mandatory for us since
092                            // SimpleDateFormat methods will succeed if less than
093                            // full string is matched.  They will also accept, 
094                            // despite "leniency" setting, a two-digit number as
095                            // a valid year (e.g. 22:04 will parse as 22 A.D.) 
096                            // so could mistakenly confuse an hour with a year, 
097                            // if we don't insist on full length parsing.
098                            if (parsed != null && pp.getIndex() == timestampStr.length()) {
099                                    working.setTime(parsed);
100                            } else {
101                                    throw new ParseException(
102                                            "Timestamp could not be parsed with older or recent DateFormat", 
103                                            pp.getIndex());
104                            }
105                    }
106                    return working;
107            }
108    
109            /**
110             * @return Returns the defaultDateFormat.
111             */
112            public SimpleDateFormat getDefaultDateFormat() {
113                    return defaultDateFormat;
114            }
115            /**
116             * @return Returns the defaultDateFormat pattern string.
117             */
118            public String getDefaultDateFormatString() {
119                    return defaultDateFormat.toPattern();
120            }
121            /**
122             * @param defaultDateFormat The defaultDateFormat to be set.
123             */
124            private void setDefaultDateFormat(String format) {
125                    if (format != null) {
126                            this.defaultDateFormat = new SimpleDateFormat(format);
127                            this.defaultDateFormat.setLenient(false);
128                    }
129            } 
130            /**
131             * @return Returns the recentDateFormat.
132             */
133            public SimpleDateFormat getRecentDateFormat() {
134                    return recentDateFormat;
135            }
136            /**
137             * @return Returns the recentDateFormat.
138             */
139            public String getRecentDateFormatString() {
140                    return recentDateFormat.toPattern();
141            }
142            /**
143             * @param recentDateFormat The recentDateFormat to set.
144             */
145            private void setRecentDateFormat(String format) {
146                    if (format != null) {
147                            this.recentDateFormat = new SimpleDateFormat(format);
148                            this.recentDateFormat.setLenient(false);
149                    }
150            }
151            
152            /**
153             * @return returns an array of 12 strings representing the short
154             * month names used by this parse.
155             */
156            public String[] getShortMonths() {
157                    return defaultDateFormat.getDateFormatSymbols().getShortMonths();
158            }
159            
160            
161            /**
162             * @return Returns the serverTimeZone used by this parser.
163             */
164            public TimeZone getServerTimeZone() {
165                    return this.defaultDateFormat.getTimeZone();
166            }
167            /**
168             * sets a TimeZone represented by the supplied ID string into all
169             * of the parsers used by this server.
170             * @param serverTimeZone Time Id java.util.TimeZone id used by
171             * the ftp server.  If null the client's local time zone is assumed.
172             */
173            private void setServerTimeZone(String serverTimeZoneId) {
174                    TimeZone serverTimeZone = TimeZone.getDefault();
175                    if (serverTimeZoneId != null) {
176                            serverTimeZone = TimeZone.getTimeZone(serverTimeZoneId);
177                    }
178                    this.defaultDateFormat.setTimeZone(serverTimeZone);
179                    if (this.recentDateFormat != null) {
180                            this.recentDateFormat.setTimeZone(serverTimeZone);
181                    }                       
182            }
183            
184            /**
185             * Implementation of the {@link  Configurable  Configurable}
186             * interface. Configures this <code>FTPTimestampParser</code> according
187             * to the following logic:
188             * <p>
189             * Set up the {@link  FTPClientConfig#setDefaultDateFormatStr(java.lang.String) defaultDateFormat}
190             * and optionally the {@link  FTPClientConfig#setRecentDateFormatStr(String) recentDateFormat}
191             * to values supplied in the config based on month names configured as follows:
192             * </p><p><ul>
193             * <li>If a {@link  FTPClientConfig#setShortMonthNames(String) shortMonthString}
194             * has been supplied in the <code>config</code>, use that to parse  parse timestamps.</li> 
195             * <li>Otherwise, if a {@link  FTPClientConfig#setServerLanguageCode(String) serverLanguageCode}
196             * has been supplied in the <code>config</code>, use the month names represented 
197             * by that {@link  FTPClientConfig#lookupDateFormatSymbols(String) language}
198             * to parse timestamps.</li>
199             * <li>otherwise use default English month names</li>
200             * </ul></p><p>
201             * Finally if a {@link  org.apache.commons.net.ftp.FTPClientConfig#setServerTimeZoneId(String) serverTimeZoneId}
202             * has been supplied via the config, set that into all date formats that have 
203             * been configured.  
204             * </p> 
205             */
206            public void configure(FTPClientConfig config) {
207                    DateFormatSymbols dfs = null;
208                    
209                    String languageCode = config.getServerLanguageCode();
210                    String shortmonths = config.getShortMonthNames();
211                    if (shortmonths != null) {
212                            dfs = FTPClientConfig.getDateFormatSymbols(shortmonths);
213                    } else if (languageCode != null) {
214                            dfs = FTPClientConfig.lookupDateFormatSymbols(languageCode);
215                    } else {
216                            dfs = FTPClientConfig.lookupDateFormatSymbols("en");
217                    }
218                    
219                    
220                    String recentFormatString = config.getRecentDateFormatStr();
221                    if (recentFormatString == null) {
222                        this.recentDateFormat = null;
223                    } else {
224                            this.recentDateFormat = new SimpleDateFormat(recentFormatString, dfs);
225                            this.recentDateFormat.setLenient(false);
226                    }
227                            
228                    String defaultFormatString = config.getDefaultDateFormatStr();
229                    if (defaultFormatString == null) {
230                            throw new IllegalArgumentException("defaultFormatString cannot be null");
231                    }
232                    this.defaultDateFormat = new SimpleDateFormat(defaultFormatString, dfs);
233                    this.defaultDateFormat.setLenient(false);
234                    
235                    setServerTimeZone(config.getServerTimeZoneId());
236            }
237    }