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    *      https://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} 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} 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       * @return Original list purged of duplicates
88       */
89      @Override
90      public List<String> preParse(final List<String> original) {
91          final HashMap<String, Integer> existingEntries = new HashMap<>();
92          final ListIterator<String> iter = original.listIterator();
93          while (iter.hasNext()) {
94              final String entry = iter.next().trim();
95              final MatchResult result;
96              final Matcher matcher = PATTERN.matcher(entry);
97              if (matcher.matches()) {
98                  result = matcher.toMatchResult();
99                  final String name = result.group(1);
100                 final String version = result.group(2);
101                 final Integer nv = Integer.valueOf(version);
102                 final Integer existing = existingEntries.get(name);
103                 if (null != existing && nv.intValue() < existing.intValue()) {
104                     iter.remove(); // removes older version from original list.
105                     continue;
106                 }
107                 existingEntries.put(name, nv);
108             }
109 
110         }
111         // we've now removed all entries less than with less than the largest
112         // version number for each name that were listed after the largest.
113         // we now must remove those with smaller than the largest version number
114         // for each name that were found before the largest
115         while (iter.hasPrevious()) {
116             final String entry = iter.previous().trim();
117             MatchResult result = null;
118             final Matcher matcher = PATTERN.matcher(entry);
119             if (matcher.matches()) {
120                 result = matcher.toMatchResult();
121                 final String name = result.group(1);
122                 final String version = result.group(2);
123                 final int nv = Integer.parseInt(version);
124                 final Integer existing = existingEntries.get(name);
125                 if (null != existing && nv < existing.intValue()) {
126                     iter.remove(); // removes older version from original list.
127                 }
128             }
129 
130         }
131         return original;
132     }
133 
134 }