001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017 018package org.apache.commons.net.ftp.parser; 019 020import java.util.HashMap; 021import java.util.List; 022import java.util.ListIterator; 023import java.util.regex.MatchResult; 024import java.util.regex.Matcher; 025import java.util.regex.Pattern; 026 027import org.apache.commons.net.ftp.FTPClientConfig; 028 029/** 030 * Special implementation VMSFTPEntryParser with versioning turned on. This parser removes all duplicates and only leaves the version with the highest version 031 * number for each file name. 032 * <p> 033 * This is a sample of VMS LIST output 034 * </p> 035 * 036 * <pre> 037 * "1-JUN.LIS;1 9/9 2-JUN-1998 07:32:04 [GROUP,OWNER] (RWED,RWED,RWED,RE)", 038 * "1-JUN.LIS;2 9/9 2-JUN-1998 07:32:04 [GROUP,OWNER] (RWED,RWED,RWED,RE)", 039 * "DATA.DIR;1 1/9 2-JUN-1998 07:32:04 [GROUP,OWNER] (RWED,RWED,RWED,RE)", 040 * </pre> 041 * 042 * @see org.apache.commons.net.ftp.FTPFileEntryParser FTPFileEntryParser (for usage instructions) 043 */ 044public class VMSVersioningFTPEntryParser extends VMSFTPEntryParser { 045 046 /** 047 * Guard against polynomial regular expression used on uncontrolled data. 048 * Don't look for more than 20 digits for the version. 049 * Don't look for more than 80 spaces after the version. 050 * Don't look for more than 80 characters after the spaces. 051 */ 052 private static final String REGEX = "(.*?);([0-9]{1,20})\\s{0,80}.{0,80}"; 053 private static final Pattern PATTERN = Pattern.compile(REGEX); 054 055 /** 056 * Constructor for a VMSFTPEntryParser object. 057 * 058 * @throws IllegalArgumentException Thrown if the regular expression is unparseable. Should not be seen under normal conditions. If the exception is seen, 059 * this is a sign that <code>REGEX</code> is not a valid regular expression. 060 */ 061 public VMSVersioningFTPEntryParser() { 062 this(null); 063 } 064 065 /** 066 * This constructor allows the creation of a VMSVersioningFTPEntryParser object with something other than the default configuration. 067 * 068 * @param config The {@link FTPClientConfig configuration} object used to configure this parser. 069 * @throws IllegalArgumentException Thrown if the regular expression is unparseable. Should not be seen under normal conditions. If the exception is seen, 070 * this is a sign that <code>REGEX</code> is not a valid regular expression. 071 * @since 1.4 072 */ 073 public VMSVersioningFTPEntryParser(final FTPClientConfig config) { 074 configure(config); 075 } 076 077 @Override 078 protected boolean isVersioning() { 079 return true; 080 } 081 082 /** 083 * Implement hook provided for those implementers (such as VMSVersioningFTPEntryParser, and possibly others) which return multiple files with the same name 084 * to remove the duplicates .. 085 * 086 * @param original Original list 087 * 088 * @return Original list purged of duplicates 089 */ 090 @Override 091 public List<String> preParse(final List<String> original) { 092 final HashMap<String, Integer> existingEntries = new HashMap<>(); 093 final ListIterator<String> iter = original.listIterator(); 094 while (iter.hasNext()) { 095 final String entry = iter.next().trim(); 096 MatchResult result; 097 final Matcher matcher = PATTERN.matcher(entry); 098 if (matcher.matches()) { 099 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}