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;
017    
018    import java.text.DateFormatSymbols;
019    import java.util.Collection;
020    import java.util.Locale;
021    import java.util.Map;
022    import java.util.StringTokenizer;
023    import java.util.TreeMap;
024    
025    /**
026     * <p>
027     * This class implements an alternate means of configuring the
028     * {@link  org.apache.commons.net.ftp.FTPClient  FTPClient} object and
029     * also subordinate objects which it uses.  Any class implementing the 
030     * {@link  org.apache.commons.net.ftp.Configurable  Configurable } 
031     * interface can be configured by this object. 
032     * </p><p>
033     * In particular this class was designed primarily to support configuration
034     * of FTP servers which express file timestamps in formats and languages 
035     * other than those for the US locale, which although it is the most common
036     * is not universal.  Unfortunately, nothing in the FTP spec allows this to 
037     * be determined in an automated way, so manual configuration such as this
038     * is necessary.
039     * </p><p>
040     * This functionality was designed to allow existing clients to work exactly
041     * as before without requiring use of this component.  This component should
042     * only need to be explicitly invoked by the user of this package for problem
043     * cases that previous implementations could not solve.
044     * </p>
045     * <h3>Examples of use of FTPClientConfig</h3>
046     * Use cases:
047     * You are trying to access a server that 
048     * <ul> 
049     * <li>lists files with timestamps that use month names in languages other 
050     * than English</li>
051     * <li>lists files with timestamps that use date formats other 
052     * than the American English "standard" <code>MM dd yyyy</code></li>
053     * <li>is in different timezone and you need accurate timestamps for 
054     * dependency checking as in Ant</li>
055     * </ul>
056     * <p>
057     * Unpaged (whole list) access on a UNIX server that uses French month names
058     * but uses the "standard" <code>MMM d yyyy</code> date formatting
059     * <pre>
060     *    FTPClient f=FTPClient();
061     *    FTPClientConfig conf = new FTPClientConfig(FTPClientConfig.SYST_UNIX);
062     *    conf.setServerLanguageCode("fr");
063     *    f.configure(conf);
064     *    f.connect(server);
065     *    f.login(username, password);
066     *    FTPFile[] files = listFiles(directory);
067     * </pre>
068     * </p>
069     * <p>
070     * Paged access on a UNIX server that uses Danish month names
071     * and "European" date formatting in Denmark's time zone, when you
072     * are in some other time zone.
073     * <pre>
074     *    FTPClient f=FTPClient();
075     *    FTPClientConfig conf = new FTPClientConfig(FTPClientConfig.SYST_UNIX);
076     *    conf.setServerLanguageCode("da");
077     *    conf.setDefaultDateFormat("d MMM yyyy");
078     *    conf.setRecentDateFormat("d MMM HH:mm");
079     *    conf.setTimeZoneId("Europe/Copenhagen");
080     *    f.configure(conf);
081     *    f.connect(server);
082     *    f.login(username, password);
083     *    FTPListParseEngine engine =
084     *       f.initiateListParsing("com.whatever.YourOwnParser", directory);
085     *
086     *    while (engine.hasNext()) {
087     *       FTPFile[] files = engine.getNext(25);  // "page size" you want
088     *       //do whatever you want with these files, display them, etc.
089     *       //expensive FTPFile objects not created until needed.
090     *    }
091     * </pre>
092     * </p> 
093     * <p>
094     * Unpaged (whole list) access on a VMS server that uses month names
095     * in a language not {@link #getSupportedLanguageCodes() supported} by the system.
096     * but uses the "standard" <code>MMM d yyyy</code> date formatting
097     * <pre>
098     *    FTPClient f=FTPClient();
099     *    FTPClientConfig conf = new FTPClientConfig(FTPClientConfig.SYST_VMS);
100     *    conf.setShortMonthNames(
101     *        "jan|feb|mar|apr|ma\u00ED|j\u00FAn|j\u00FAl|\u00e1g\u00FA|sep|okt|n\u00F3v|des");
102     *    f.configure(conf);
103     *    f.connect(server);
104     *    f.login(username, password);
105     *    FTPFile[] files = listFiles(directory);
106     * </pre>
107     * </p>
108     * <p>
109     * Unpaged (whole list) access on a Windows-NT server in a different time zone.
110     * (Note, since the NT Format uses numeric date formatting, language issues
111     * are irrelevant here).
112     * <pre>
113     *    FTPClient f=FTPClient();
114     *    FTPClientConfig conf = new FTPClientConfig(FTPClientConfig.SYST_NT);
115     *    conf.setTimeZoneId("America/Denver");
116     *    f.configure(conf);
117     *    f.connect(server);
118     *    f.login(username, password);
119     *    FTPFile[] files = listFiles(directory);
120     * </pre>
121     * </p>
122     * Unpaged (whole list) access on a Windows-NT server in a different time zone
123     * but which has been configured to use a unix-style listing format.
124     * <pre>
125     *    FTPClient f=FTPClient();
126     *    FTPClientConfig conf = new FTPClientConfig(FTPClientConfig.SYST_UNIX);
127     *    conf.setTimeZoneId("America/Denver");
128     *    f.configure(conf);
129     *    f.connect(server);
130     *    f.login(username, password);
131     *    FTPFile[] files = listFiles(directory);
132     * </pre>
133     * </p>
134     * @since 1.4
135     * @see org.apache.commons.net.ftp.Configurable
136     * @see org.apache.commons.net.ftp.FTPClient
137     * @see org.apache.commons.net.ftp.parser.FTPTimestampParserImpl#configure(FTPClientConfig)
138     * @see org.apache.commons.net.ftp.parser.ConfigurableFTPFileEntryParserImpl
139     */
140    public class FTPClientConfig
141    {
142            
143        /**
144         * Identifier by which a unix-based ftp server is known throughout
145         * the commons-net ftp system.
146         */
147        public static final String SYST_UNIX  = "UNIX";
148    
149        /**
150         * Identifier by which a vms-based ftp server is known throughout
151         * the commons-net ftp system.
152         */
153        public static final String SYST_VMS   = "VMS";
154        
155        /**
156         * Identifier by which a WindowsNT-based ftp server is known throughout
157         * the commons-net ftp system.
158         */
159        public static final String SYST_NT    = "WINDOWS";
160    
161        /**
162         * Identifier by which an OS/2-based ftp server is known throughout
163         * the commons-net ftp system.
164         */
165        public static final String SYST_OS2   = "OS/2";
166    
167        /**
168         * Identifier by which an OS/400-based ftp server is known throughout
169         * the commons-net ftp system.
170         */
171        public static final String SYST_OS400 = "OS/400";
172        
173        /**
174         * Identifier by which an MVS-based ftp server is known throughout
175         * the commons-net ftp system.
176         */
177        public static final String SYST_MVS = "MVS";
178        
179        private final String serverSystemKey;
180            private String defaultDateFormatStr = null;
181            private String recentDateFormatStr = null;
182            private String serverLanguageCode = null;
183            private String shortMonthNames = null;
184            private String serverTimeZoneId = null;
185            
186            
187            /**
188             * The main constructor for an FTPClientConfig object
189             * @param systemKey key representing system type of the  server being 
190             * connected to. See {@link #getServerSystemKey() serverSystemKey}
191             */
192            public FTPClientConfig(String systemKey) {
193                    this.serverSystemKey = systemKey;
194            }
195    
196            /**
197             * Convenience constructor mainly for use in testing.
198             * Constructs a UNIX configuration. 
199             */
200            public FTPClientConfig() {
201                this(SYST_UNIX);
202            }
203    
204            /**
205             * Constructor which allows setting of all member fields
206             * @param systemKey key representing system type of the  server being 
207             * connected to. See 
208             *  {@link #getServerSystemKey() serverSystemKey}
209             * @param defaultDateFormatStr See 
210             *      {@link  #setDefaultDateFormatStr(String)  defaultDateFormatStr}
211             * @param recentDateFormatStr See
212             *      {@link  #setRecentDateFormatStr(String)  recentDateFormatStr}
213             * @param serverLanguageCode See
214             *      {@link  #setServerLanguageCode(String)  serverLanguageCode}
215             * @param shortMonthNames See
216             *      {@link  #setShortMonthNames(String)  shortMonthNames}
217             * @param serverTimeZoneId See
218             *      {@link  #setServerTimeZoneId(String)  serverTimeZoneId}
219             */
220            public FTPClientConfig(String systemKey,
221                                               String defaultDateFormatStr,
222                                               String recentDateFormatStr,
223                                               String serverLanguageCode,
224                                               String shortMonthNames,
225                                               String serverTimeZoneId)
226            {
227                this(systemKey);
228                    this.defaultDateFormatStr = defaultDateFormatStr;
229                    this.recentDateFormatStr = recentDateFormatStr;
230                    this.serverLanguageCode = serverLanguageCode;
231                    this.shortMonthNames = shortMonthNames;
232                    this.serverTimeZoneId = serverTimeZoneId;
233            }
234            
235            private static Map LANGUAGE_CODE_MAP = new TreeMap();
236            static {
237                    
238                    // if there are other commonly used month name encodings which
239                    // correspond to particular locales, please add them here.
240                    
241                    
242                    
243                    // many locales code short names for months as all three letters
244                    // these we handle simply.
245                    LANGUAGE_CODE_MAP.put("en", Locale.ENGLISH);
246                    LANGUAGE_CODE_MAP.put("de",Locale.GERMAN);
247                    LANGUAGE_CODE_MAP.put("it",Locale.ITALIAN);
248                    LANGUAGE_CODE_MAP.put("es", new Locale("es", "", "")); // spanish
249                    LANGUAGE_CODE_MAP.put("pt", new Locale("pt", "", "")); // portuguese
250                    LANGUAGE_CODE_MAP.put("da", new Locale("da", "", "")); // danish
251                    LANGUAGE_CODE_MAP.put("sv", new Locale("sv", "", "")); // swedish
252                    LANGUAGE_CODE_MAP.put("no", new Locale("no", "", "")); // norwegian
253                    LANGUAGE_CODE_MAP.put("nl", new Locale("nl", "", "")); // dutch
254                    LANGUAGE_CODE_MAP.put("ro", new Locale("ro", "", "")); // romanian
255                    LANGUAGE_CODE_MAP.put("sq", new Locale("sq", "", "")); // albanian
256                    LANGUAGE_CODE_MAP.put("sh", new Locale("sh", "", "")); // serbo-croatian
257                    LANGUAGE_CODE_MAP.put("sk", new Locale("sk", "", "")); // slovak                
258                    LANGUAGE_CODE_MAP.put("sl", new Locale("sl", "", "")); // slovenian
259    
260    
261                    // some don't
262                    LANGUAGE_CODE_MAP.put("fr",     
263                            "jan|f\u00e9v|mar|avr|mai|jun|jui|ao\u00fb|sep|oct|nov|d\u00e9c");  //french
264                            
265            }
266            
267            /**
268             * Getter for the serverSystemKey property.  This property
269         * specifies the general type of server to which the client connects.
270         * Should be either one of the <code>FTPClientConfig.SYST_*</code> codes
271         * or else the fully qualified class name of a parser implementing both
272         * the <code>FTPFileEntryParser</code> and <code>Configurable</code>
273         * interfaces.
274             * @return Returns the serverSystemKey property.
275             */
276            public String getServerSystemKey() {
277                    return serverSystemKey;
278            }
279            
280            /**
281             * getter for the {@link  #setDefaultDateFormatStr(String)  defaultDateFormatStr} 
282             * property.  
283             * @return Returns the defaultDateFormatStr property.
284             */
285            public String getDefaultDateFormatStr() {
286                    return defaultDateFormatStr;
287            }
288            
289            /**
290             * getter for the {@link  #setRecentDateFormatStr(String)  recentDateFormatStr} property.
291             * @return Returns the recentDateFormatStr property.
292             */
293    
294            public String getRecentDateFormatStr() {
295                    return recentDateFormatStr;
296            }
297            
298            /**
299             * getter for the {@link  #setServerTimeZoneId(String)  serverTimeZoneId} property.
300             * @return Returns the serverTimeZoneId property.
301             */
302            public String getServerTimeZoneId() {
303                    return serverTimeZoneId;
304            }
305            
306            /**
307             * <p>
308             * getter for the {@link  #setShortMonthNames(String)  shortMonthNames} 
309             * property.  
310             * </p>
311             * @return Returns the shortMonthNames.
312             */
313            public String getShortMonthNames() {
314                    return shortMonthNames;
315            }
316            
317            /**
318             * <p>
319             * getter for the {@link  #setServerLanguageCode(String)  serverLanguageCode} property.
320             * </p>  
321    *        * @return Returns the serverLanguageCode property.
322             */
323            public String getServerLanguageCode() {
324                    return serverLanguageCode;
325            }
326            
327            /**
328             * <p>
329             * setter for the defaultDateFormatStr property.  This property
330             * specifies the main date format that will be used by a parser configured
331             * by this configuration to parse file timestamps.  If this is not
332             * specified, such a parser will use as a default value, the most commonly
333             * used format which will be in as used in <code>en_US</code> locales.
334             * </p><p>
335             * This should be in the format described for 
336             * <code>java.text.SimpleDateFormat</code>. 
337             * property.
338             * </p>
339             * @param defaultDateFormatStr The defaultDateFormatStr to set.
340             */
341            public void setDefaultDateFormatStr(String defaultDateFormatStr) {
342                    this.defaultDateFormatStr = defaultDateFormatStr;
343            }
344            
345            /**
346             * <p>
347             * setter for the recentDateFormatStr property.  This property
348             * specifies a secondary date format that will be used by a parser 
349             * configured by this configuration to parse file timestamps, typically 
350             * those less than a year old.  If this is  not specified, such a parser 
351             * will not attempt to parse using an alternate format.
352             * </p>
353             * This is used primarily in unix-based systems.
354             * </p>
355             * This should be in the format described for 
356             * <code>java.text.SimpleDateFormat</code>.
357             * </p>
358             * @param recentDateFormatStr The recentDateFormatStr to set.
359             */
360            public void setRecentDateFormatStr(String recentDateFormatStr) {
361                    this.recentDateFormatStr = recentDateFormatStr;
362            }
363            
364            /**
365             * <p>
366             * setter for the serverTimeZoneId property.  This property
367             * allows a time zone to be specified corresponding to that known to be 
368             * used by an FTP server in file listings.  This might be particularly 
369             * useful to clients such as Ant that try to use these timestamps for 
370             * dependency checking.
371             * </p><p>
372             * This should be one of the identifiers used by 
373             * <code>java.util.TimeZone</code> to refer to time zones, for example, 
374             * <code>America/Chicago</code> or <code>Asia/Rangoon</code>.
375             * </p>
376             * @param serverTimeZoneId The serverTimeZoneId to set.
377             */
378            public void setServerTimeZoneId(String serverTimeZoneId) {
379                    this.serverTimeZoneId = serverTimeZoneId;
380            }
381            
382            /**
383             * <p>
384             * setter for the shortMonthNames property.  
385             * This property allows the user to specify a set of month names
386             * used by the server that is different from those that may be 
387             * specified using the {@link  #setServerLanguageCode(String)  serverLanguageCode}
388             * property.
389             * </p><p>
390             * This should be a string containing twelve strings each composed of
391             * three characters, delimited by pipe (|) characters.  Currently, 
392             * only 8-bit ASCII characters are known to be supported.  For example,
393             * a set of month names used by a hypothetical Icelandic FTP server might 
394             * conceivably be specified as 
395             * <code>"jan|feb|mar|apr|ma&#xED;|j&#xFA;n|j&#xFA;l|&#xE1;g&#xFA;|sep|okt|n&#xF3;v|des"</code>.  
396             * </p>
397             * @param shortMonthNames The value to set to the shortMonthNames property.
398             */
399            public void setShortMonthNames(String shortMonthNames) {
400                    this.shortMonthNames = shortMonthNames;
401            }
402            
403            /**
404             * <p>
405             * setter for the serverLanguageCode property.  This property allows
406             * user to specify a 
407             * <a href="http://www.ics.uci.edu/pub/ietf/http/related/iso639.txt">
408             * two-letter ISO-639 language code</a> that will be used to 
409             * configure the set of month names used by the file timestamp parser.
410             * If neither this nor the {@link #setShortMonthNames(String) shortMonthNames} 
411             * is specified, parsing will assume English month names, which may or 
412             * may not be significant, depending on whether the date format(s) 
413             * specified via {@link  #setDefaultDateFormatStr(String)  defaultDateFormatStr} 
414             * and/or {@link  #setRecentDateFormatStr(String)  recentDateFormatStr} are using 
415             * numeric or alphabetic month names.
416             * </p>
417             * <p>If the code supplied is not supported here, <code>en_US</code>
418             * month names will be used.  We are supporting here those language 
419             * codes which, when a <code> java.util.Locale</code> is constucted
420             * using it, and a <code>java.text.SimpleDateFormat</code> is 
421             * constructed using that Locale, the array returned by the 
422             * SimpleDateFormat's <code>getShortMonths()</code> method consists
423             * solely of three 8-bit ASCII character strings.  Additionally, 
424             * languages which do not meet this requirement are included if a 
425             * common alternative set of short month names is known to be used.
426             * This means that users who can tell us of additional such encodings
427             * may get them added to the list of supported languages by contacting
428             * the jakarta-commons-net team.
429             * </p>
430             * <p><strong>
431             * Please note that this attribute will NOT be used to determine a 
432             * locale-based date format for the language.  </strong>  
433             * Experience has shown that many if not most FTP servers outside the
434             * United States employ the standard <code>en_US</code> date format 
435             * orderings of <code>MMM d yyyy</code> and <code>MMM d HH:mm</code> 
436             * and attempting to deduce this automatically here would cause more
437             * problems than it would solve.  The date format must be changed 
438             * via the {@link  #setDefaultDateFormatStr(String)  defaultDateFormatStr} and/or 
439             * {@link  #setRecentDateFormatStr(String)  recentDateFormatStr} parameters.
440             * </p>     
441             * @param serverLanguageCode The value to set to the serverLanguageCode property.  
442             */
443            public void setServerLanguageCode(String serverLanguageCode) {
444                    this.serverLanguageCode = serverLanguageCode;
445            }
446            
447            /**
448             * Looks up the supplied language code in the internally maintained table of 
449             * language codes.  Returns a DateFormatSymbols object configured with 
450             * short month names corresponding to the code.  If there is no corresponding
451             * entry in the table, the object returned will be that for 
452             * <code>Locale.US</code> 
453             * @param languageCode See {@link  #setServerLanguageCode(String)  serverLanguageCode}
454             * @return a DateFormatSymbols object configured with short month names 
455             * corresponding to the supplied code, or with month names for  
456             * <code>Locale.US</code> if there is no corresponding entry in the internal
457             * table.
458             */
459            public static DateFormatSymbols lookupDateFormatSymbols(String languageCode) 
460            {
461                    Object lang = LANGUAGE_CODE_MAP.get(languageCode);
462                    if (lang != null) {
463                            if (lang instanceof Locale) {
464                                    return new DateFormatSymbols((Locale) lang);
465                            } else if (lang instanceof String){
466                                    return getDateFormatSymbols((String) lang);
467                            }
468                    }
469                    return new DateFormatSymbols(Locale.US);
470            }
471            
472            /**
473             * Returns a DateFormatSymbols object configured with short month names
474             * as in the supplied string
475             * @param shortmonths This  should be as described in 
476             *  {@link  #setShortMonthNames(String)  shortMonthNames}
477             * @return a DateFormatSymbols object configured with short month names
478             * as in the supplied string
479             */
480            public static DateFormatSymbols getDateFormatSymbols(String shortmonths) 
481            {
482                    String[] months = splitShortMonthString(shortmonths);
483                    DateFormatSymbols dfs = new DateFormatSymbols(Locale.US);
484                    dfs.setShortMonths(months);
485                    return dfs;
486            }
487            
488            private static String[] splitShortMonthString(String shortmonths) {
489                    StringTokenizer st = new StringTokenizer(shortmonths, "|");
490                    int monthcnt = st.countTokens();
491                    if (12 != monthcnt) {
492                            throw new IllegalArgumentException(
493                                            "expecting a pipe-delimited string containing 12 tokens");
494                    }
495                    String[] months = new String[13];
496                    int pos = 0;
497                    while(st.hasMoreTokens()) {
498                            months[pos++] = st.nextToken();
499                    }
500                    months[pos]="";
501                    return months;
502            }
503    
504            /**
505             * Returns a Collection of all the language codes currently supported
506             * by this class. See {@link  #setServerLanguageCode(String)  serverLanguageCode}  
507             * for a functional descrption of language codes within this system. 
508             *      
509             * @return a Collection of all the language codes currently supported
510             * by this class
511             */
512            public static Collection getSupportedLanguageCodes() {
513                return LANGUAGE_CODE_MAP.keySet();
514            }
515            
516            
517    }