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.util.HashMap;
21  import java.util.List;
22  import java.util.ListIterator;
23  import java.util.regex.MatchResult;
24  import java.util.regex.Matcher;
25  import java.util.regex.Pattern;
26  
27  import org.apache.commons.net.ftp.FTPClientConfig;
28  
29  /**
30   * Special implementation VMSFTPEntryParser with versioning turned on. This parser removes all duplicates and only leaves the version with the highest version
31   * number for each file name.
32   * <p>
33   * This is a sample of VMS LIST output
34   * </p>
35   *
36   * <pre>
37   *  "1-JUN.LIS;1              9/9           2-JUN-1998 07:32:04  [GROUP,OWNER]    (RWED,RWED,RWED,RE)",
38   *  "1-JUN.LIS;2              9/9           2-JUN-1998 07:32:04  [GROUP,OWNER]    (RWED,RWED,RWED,RE)",
39   *  "DATA.DIR;1               1/9           2-JUN-1998 07:32:04  [GROUP,OWNER]    (RWED,RWED,RWED,RE)",
40   * </pre>
41   *
42   * @see org.apache.commons.net.ftp.FTPFileEntryParser FTPFileEntryParser (for usage instructions)
43   */
44  public class VMSVersioningFTPEntryParser extends VMSFTPEntryParser {
45  
46      /**
47       * Guard against polynomial regular expression used on uncontrolled data.
48       * Don't look for more than 20 digits for the version.
49       * Don't look for more than 80 spaces after the version.
50       * Don't look for more than 80 characters after the spaces.
51       */
52      private static final String REGEX = "(.*?);([0-9]{1,20})\\s{0,80}.{0,80}";
53      private static final Pattern PATTERN = Pattern.compile(REGEX);
54  
55      /**
56       * Constructor for a VMSFTPEntryParser object.
57       *
58       * @throws IllegalArgumentException Thrown if the regular expression is unparseable. Should not be seen under normal conditions. If the exception is seen,
59       *                                  this is a sign that <code>REGEX</code> is not a valid regular expression.
60       */
61      public VMSVersioningFTPEntryParser() {
62          this(null);
63      }
64  
65      /**
66       * This constructor allows the creation of a VMSVersioningFTPEntryParser object with something other than the default configuration.
67       *
68       * @param config The {@link FTPClientConfig configuration} object used to configure this parser.
69       * @throws IllegalArgumentException Thrown if the regular expression is unparseable. Should not be seen under normal conditions. If the exception is seen,
70       *                                  this is a sign that <code>REGEX</code> is not a valid regular expression.
71       * @since 1.4
72       */
73      public VMSVersioningFTPEntryParser(final FTPClientConfig config) {
74          configure(config);
75      }
76  
77      @Override
78      protected boolean isVersioning() {
79          return true;
80      }
81  
82      /**
83       * Implement hook provided for those implementers (such as VMSVersioningFTPEntryParser, and possibly others) which return multiple files with the same name
84       * to remove the duplicates ..
85       *
86       * @param original Original list
87       *
88       * @return Original list purged of duplicates
89       */
90      @Override
91      public List<String> preParse(final List<String> original) {
92          final HashMap<String, Integer> existingEntries = new HashMap<>();
93          final ListIterator<String> iter = original.listIterator();
94          while (iter.hasNext()) {
95              final String entry = iter.next().trim();
96              MatchResult result;
97              final Matcher matcher = PATTERN.matcher(entry);
98              if (matcher.matches()) {
99                  result = matcher.toMatchResult();
100                 final String name = result.group(1);
101                 final String version = result.group(2);
102                 final Integer nv = Integer.valueOf(version);
103                 final Integer existing = existingEntries.get(name);
104                 if (null != existing && nv.intValue() < existing.intValue()) {
105                     iter.remove(); // removes older version from original list.
106                     continue;
107                 }
108                 existingEntries.put(name, nv);
109             }
110 
111         }
112         // we've now removed all entries less than with less than the largest
113         // version number for each name that were listed after the largest.
114         // we now must remove those with smaller than the largest version number
115         // for each name that were found before the largest
116         while (iter.hasPrevious()) {
117             final String entry = iter.previous().trim();
118             MatchResult result = null;
119             final Matcher matcher = PATTERN.matcher(entry);
120             if (matcher.matches()) {
121                 result = matcher.toMatchResult();
122                 final String name = result.group(1);
123                 final String version = result.group(2);
124                 final int nv = Integer.parseInt(version);
125                 final Integer existing = existingEntries.get(name);
126                 if (null != existing && nv < existing.intValue()) {
127                     iter.remove(); // removes older version from original list.
128                 }
129             }
130 
131         }
132         return original;
133     }
134 
135 }