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; 026import java.util.regex.PatternSyntaxException; 027 028import org.apache.commons.net.ftp.FTPClientConfig; 029 030/** 031 * Special implementation VMSFTPEntryParser with versioning turned on. This parser removes all duplicates and only leaves the version with the highest version 032 * number for each file name. 033 * <p> 034 * This is a sample of VMS LIST output 035 * </p> 036 * 037 * <pre> 038 * "1-JUN.LIS;1 9/9 2-JUN-1998 07:32:04 [GROUP,OWNER] (RWED,RWED,RWED,RE)", 039 * "1-JUN.LIS;2 9/9 2-JUN-1998 07:32:04 [GROUP,OWNER] (RWED,RWED,RWED,RE)", 040 * "DATA.DIR;1 1/9 2-JUN-1998 07:32:04 [GROUP,OWNER] (RWED,RWED,RWED,RE)", 041 * </pre> 042 * 043 * @see org.apache.commons.net.ftp.FTPFileEntryParser FTPFileEntryParser (for usage instructions) 044 */ 045public class VMSVersioningFTPEntryParser extends VMSFTPEntryParser { 046 047 private static final String PRE_PARSE_REGEX = "(.*?);([0-9]+)\\s*.*"; 048 private final Pattern preparsePattern; 049 050 /** 051 * Constructor for a VMSFTPEntryParser object. 052 * 053 * @throws IllegalArgumentException Thrown if the regular expression is unparseable. Should not be seen under normal conditions. If the exception is seen, 054 * this is a sign that <code>REGEX</code> is not a valid regular expression. 055 */ 056 public VMSVersioningFTPEntryParser() { 057 this(null); 058 } 059 060 /** 061 * This constructor allows the creation of a VMSVersioningFTPEntryParser object with something other than the default configuration. 062 * 063 * @param config The {@link FTPClientConfig configuration} object used to configure this parser. 064 * @throws IllegalArgumentException Thrown if the regular expression is unparseable. Should not be seen under normal conditions. If the exception is seen, 065 * this is a sign that <code>REGEX</code> is not a valid regular expression. 066 * @since 1.4 067 */ 068 public VMSVersioningFTPEntryParser(final FTPClientConfig config) { 069 configure(config); 070 try { 071 // _preparse_matcher_ = new Perl5Matcher(); 072 preparsePattern = Pattern.compile(PRE_PARSE_REGEX); 073 } catch (final PatternSyntaxException pse) { 074 throw new IllegalArgumentException("Unparseable regex supplied: " + PRE_PARSE_REGEX); 075 } 076 077 } 078 079 @Override 080 protected boolean isVersioning() { 081 return true; 082 } 083 084 /** 085 * Implement hook provided for those implementers (such as VMSVersioningFTPEntryParser, and possibly others) which return multiple files with the same name 086 * to remove the duplicates .. 087 * 088 * @param original Original list 089 * 090 * @return Original list purged of duplicates 091 */ 092 @Override 093 public List<String> preParse(final List<String> original) { 094 final HashMap<String, Integer> existingEntries = new HashMap<>(); 095 final ListIterator<String> iter = original.listIterator(); 096 while (iter.hasNext()) { 097 final String entry = iter.next().trim(); 098 MatchResult result; 099 final Matcher _preparse_matcher_ = preparsePattern.matcher(entry); 100 if (_preparse_matcher_.matches()) { 101 result = _preparse_matcher_.toMatchResult(); 102 final String name = result.group(1); 103 final String version = result.group(2); 104 final Integer nv = Integer.valueOf(version); 105 final Integer existing = existingEntries.get(name); 106 if (null != existing && nv.intValue() < existing.intValue()) { 107 iter.remove(); // removes older version from original list. 108 continue; 109 } 110 existingEntries.put(name, nv); 111 } 112 113 } 114 // we've now removed all entries less than with less than the largest 115 // version number for each name that were listed after the largest. 116 // we now must remove those with smaller than the largest version number 117 // for each name that were found before the largest 118 while (iter.hasPrevious()) { 119 final String entry = iter.previous().trim(); 120 MatchResult result = null; 121 final Matcher _preparse_matcher_ = preparsePattern.matcher(entry); 122 if (_preparse_matcher_.matches()) { 123 result = _preparse_matcher_.toMatchResult(); 124 final String name = result.group(1); 125 final String version = result.group(2); 126 final int nv = Integer.parseInt(version); 127 final Integer existing = existingEntries.get(name); 128 if (null != existing && nv < existing.intValue()) { 129 iter.remove(); // removes older version from original list. 130 } 131 } 132 133 } 134 return original; 135 } 136 137}