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 *      https://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.regex.MatchResult;
021import java.util.regex.Matcher;
022import java.util.regex.Pattern;
023import java.util.regex.PatternSyntaxException;
024
025import org.apache.commons.net.ftp.FTPFileEntryParserImpl;
026
027/**
028 * This abstract class implements both the older FTPFileListParser and newer FTPFileEntryParser interfaces with default functionality. All the classes in the
029 * parser subpackage inherit from this.
030 * <p>
031 * This is the base class for all regular expression based FTPFileEntryParser classes
032 * </p>
033 */
034public abstract class RegexFTPFileEntryParserImpl extends FTPFileEntryParserImpl {
035
036    /**
037     * Internal pattern the matcher tries to match, representing a file entry
038     */
039    private Pattern pattern;
040
041    /**
042     * Internal match result used by the parser
043     */
044    private MatchResult result;
045
046    /**
047     * Internal PatternMatcher object used by the parser. It has protected scope in case subclasses want to make use of it for their own purposes.
048     */
049    protected Matcher _matcher_;
050
051    /**
052     * The constructor for a RegexFTPFileEntryParserImpl object. The expression is compiled with flags = 0.
053     *
054     * @param regex The regular expression with which this object is initialized.
055     * @throws IllegalArgumentException Thrown if the regular expression is unparseable. Should not be seen in normal conditions. If it is seen, this is a sign
056     *                                  that a subclass has been created with a bad regular expression. Since the parser must be created before use, this means
057     *                                  that any bad parser subclasses created from this will bomb very quickly, leading to easy detection.
058     */
059
060    public RegexFTPFileEntryParserImpl(final String regex) {
061        compileRegex(regex, 0);
062    }
063
064    /**
065     * The constructor for a RegexFTPFileEntryParserImpl object.
066     *
067     * @param regex The regular expression with which this object is initialized.
068     * @param flags the flags to apply, see {@link Pattern#compile(String, int)}. Use 0 for none.
069     * @throws IllegalArgumentException Thrown if the regular expression is unparseable. Should not be seen in normal conditions. If it is seen, this is a sign
070     *                                  that a subclass has been created with a bad regular expression. Since the parser must be created before use, this means
071     *                                  that any bad parser subclasses created from this will bomb very quickly, leading to easy detection.
072     * @since 3.4
073     */
074    public RegexFTPFileEntryParserImpl(final String regex, final int flags) {
075        compileRegex(regex, flags);
076    }
077
078    /**
079     * Compile the regex and store the {@link Pattern}.
080     *
081     * This is an internal method to do the work so the constructor does not have to call an overrideable method.
082     *
083     * @param regex the expression to compile
084     * @param flags the flags to apply, see {@link Pattern#compile(String, int)}. Use 0 for none.
085     * @throws IllegalArgumentException if the regex cannot be compiled
086     */
087    private void compileRegex(final String regex, final int flags) {
088        try {
089            pattern = Pattern.compile(regex, flags);
090        } catch (final PatternSyntaxException pse) {
091            throw new IllegalArgumentException("Unparseable regex supplied: " + regex);
092        }
093    }
094
095    /**
096     * Convenience method
097     *
098     * @return the number of groups() in the internal MatchResult.
099     */
100    public int getGroupCnt() {
101        if (result == null) {
102            return 0;
103        }
104        return result.groupCount();
105    }
106
107    /**
108     * Gets a string shows each match group by number.
109     * <p>
110     * For debugging purposes.
111     * </p>
112     *
113     * @return a string shows each match group by number.
114     */
115    public String getGroupsAsString() {
116        final StringBuilder b = new StringBuilder();
117        for (int i = 1; i <= result.groupCount(); i++) {
118            b.append(i).append(") ").append(result.group(i)).append(System.lineSeparator());
119        }
120        return b.toString();
121    }
122
123    /**
124     * Convenience method delegates to the internal MatchResult's group() method.
125     *
126     * @param matchNum match group number to be retrieved
127     * @return the content of the {@code matchnum'th} group of the internal match or null if this method is called without a match having been made.
128     */
129    public String group(final int matchNum) {
130        if (result == null) {
131            return null;
132        }
133        return result.group(matchNum);
134    }
135
136    /**
137     * Convenience method delegates to the internal MatchResult's matches() method.
138     *
139     * @param s the String to be matched
140     * @return true if s matches this object's regular expression.
141     */
142    public boolean matches(final String s) {
143        result = null;
144        _matcher_ = pattern.matcher(s);
145        if (_matcher_.matches()) {
146            result = _matcher_.toMatchResult();
147        }
148        return null != result;
149    }
150
151    /**
152     * Sets the regular expression for entry parsing and create a new {@link Pattern} instance.
153     *
154     * @param regex The new regular expression
155     * @return true
156     * @throws IllegalArgumentException if the regex cannot be compiled
157     * @since 2.0
158     */
159    public boolean setRegex(final String regex) {
160        compileRegex(regex, 0);
161        return true;
162    }
163
164    /**
165     * Sets the regular expression for entry parsing and create a new {@link Pattern} instance.
166     *
167     * @param regex The new regular expression
168     * @param flags the flags to apply, see {@link Pattern#compile(String, int)}. Use 0 for none.
169     * @return true
170     * @throws IllegalArgumentException if the regex cannot be compiled
171     * @since 3.4
172     */
173    public boolean setRegex(final String regex, final int flags) {
174        compileRegex(regex, flags);
175        return true;
176    }
177}