001/*
002 * Copyright 2002-2018 the original author or authors.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *      https://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016
017package org.springframework.aop.support;
018
019import java.io.Serializable;
020import java.lang.reflect.Method;
021import java.util.Arrays;
022
023import org.springframework.lang.Nullable;
024import org.springframework.util.Assert;
025import org.springframework.util.ClassUtils;
026import org.springframework.util.ObjectUtils;
027import org.springframework.util.StringUtils;
028
029/**
030 * Abstract base regular expression pointcut bean. JavaBean properties are:
031 * <ul>
032 * <li>pattern: regular expression for the fully-qualified method names to match.
033 * The exact regexp syntax will depend on the subclass (e.g. Perl5 regular expressions)
034 * <li>patterns: alternative property taking a String array of patterns.
035 * The result will be the union of these patterns.
036 * </ul>
037 *
038 * <p>Note: the regular expressions must be a match. For example,
039 * {@code .*get.*} will match com.mycom.Foo.getBar().
040 * {@code get.*} will not.
041 *
042 * <p>This base class is serializable. Subclasses should declare all fields transient;
043 * the {@link #initPatternRepresentation} method will be invoked again on deserialization.
044 *
045 * @author Rod Johnson
046 * @author Juergen Hoeller
047 * @author Rob Harrop
048 * @since 1.1
049 * @see JdkRegexpMethodPointcut
050 */
051@SuppressWarnings("serial")
052public abstract class AbstractRegexpMethodPointcut extends StaticMethodMatcherPointcut
053                implements Serializable {
054
055        /**
056         * Regular expressions to match.
057         */
058        private String[] patterns = new String[0];
059
060        /**
061         * Regular expressions <strong>not</strong> to match.
062         */
063        private String[] excludedPatterns = new String[0];
064
065
066        /**
067         * Convenience method when we have only a single pattern.
068         * Use either this method or {@link #setPatterns}, not both.
069         * @see #setPatterns
070         */
071        public void setPattern(String pattern) {
072                setPatterns(pattern);
073        }
074
075        /**
076         * Set the regular expressions defining methods to match.
077         * Matching will be the union of all these; if any match, the pointcut matches.
078         * @see #setPattern
079         */
080        public void setPatterns(String... patterns) {
081                Assert.notEmpty(patterns, "'patterns' must not be empty");
082                this.patterns = new String[patterns.length];
083                for (int i = 0; i < patterns.length; i++) {
084                        this.patterns[i] = StringUtils.trimWhitespace(patterns[i]);
085                }
086                initPatternRepresentation(this.patterns);
087        }
088
089        /**
090         * Return the regular expressions for method matching.
091         */
092        public String[] getPatterns() {
093                return this.patterns;
094        }
095
096        /**
097         * Convenience method when we have only a single exclusion pattern.
098         * Use either this method or {@link #setExcludedPatterns}, not both.
099         * @see #setExcludedPatterns
100         */
101        public void setExcludedPattern(String excludedPattern) {
102                setExcludedPatterns(excludedPattern);
103        }
104
105        /**
106         * Set the regular expressions defining methods to match for exclusion.
107         * Matching will be the union of all these; if any match, the pointcut matches.
108         * @see #setExcludedPattern
109         */
110        public void setExcludedPatterns(String... excludedPatterns) {
111                Assert.notEmpty(excludedPatterns, "'excludedPatterns' must not be empty");
112                this.excludedPatterns = new String[excludedPatterns.length];
113                for (int i = 0; i < excludedPatterns.length; i++) {
114                        this.excludedPatterns[i] = StringUtils.trimWhitespace(excludedPatterns[i]);
115                }
116                initExcludedPatternRepresentation(this.excludedPatterns);
117        }
118
119        /**
120         * Returns the regular expressions for exclusion matching.
121         */
122        public String[] getExcludedPatterns() {
123                return this.excludedPatterns;
124        }
125
126
127        /**
128         * Try to match the regular expression against the fully qualified name
129         * of the target class as well as against the method's declaring class,
130         * plus the name of the method.
131         */
132        @Override
133        public boolean matches(Method method, Class<?> targetClass) {
134                return (matchesPattern(ClassUtils.getQualifiedMethodName(method, targetClass)) ||
135                                (targetClass != method.getDeclaringClass() &&
136                                                matchesPattern(ClassUtils.getQualifiedMethodName(method, method.getDeclaringClass()))));
137        }
138
139        /**
140         * Match the specified candidate against the configured patterns.
141         * @param signatureString "java.lang.Object.hashCode" style signature
142         * @return whether the candidate matches at least one of the specified patterns
143         */
144        protected boolean matchesPattern(String signatureString) {
145                for (int i = 0; i < this.patterns.length; i++) {
146                        boolean matched = matches(signatureString, i);
147                        if (matched) {
148                                for (int j = 0; j < this.excludedPatterns.length; j++) {
149                                        boolean excluded = matchesExclusion(signatureString, j);
150                                        if (excluded) {
151                                                return false;
152                                        }
153                                }
154                                return true;
155                        }
156                }
157                return false;
158        }
159
160
161        /**
162         * Subclasses must implement this to initialize regexp pointcuts.
163         * Can be invoked multiple times.
164         * <p>This method will be invoked from the {@link #setPatterns} method,
165         * and also on deserialization.
166         * @param patterns the patterns to initialize
167         * @throws IllegalArgumentException in case of an invalid pattern
168         */
169        protected abstract void initPatternRepresentation(String[] patterns) throws IllegalArgumentException;
170
171        /**
172         * Subclasses must implement this to initialize regexp pointcuts.
173         * Can be invoked multiple times.
174         * <p>This method will be invoked from the {@link #setExcludedPatterns} method,
175         * and also on deserialization.
176         * @param patterns the patterns to initialize
177         * @throws IllegalArgumentException in case of an invalid pattern
178         */
179        protected abstract void initExcludedPatternRepresentation(String[] patterns) throws IllegalArgumentException;
180
181        /**
182         * Does the pattern at the given index match the given String?
183         * @param pattern the {@code String} pattern to match
184         * @param patternIndex index of pattern (starting from 0)
185         * @return {@code true} if there is a match, {@code false} otherwise
186         */
187        protected abstract boolean matches(String pattern, int patternIndex);
188
189        /**
190         * Does the exclusion pattern at the given index match the given String?
191         * @param pattern the {@code String} pattern to match
192         * @param patternIndex index of pattern (starting from 0)
193         * @return {@code true} if there is a match, {@code false} otherwise
194         */
195        protected abstract boolean matchesExclusion(String pattern, int patternIndex);
196
197
198        @Override
199        public boolean equals(@Nullable Object other) {
200                if (this == other) {
201                        return true;
202                }
203                if (!(other instanceof AbstractRegexpMethodPointcut)) {
204                        return false;
205                }
206                AbstractRegexpMethodPointcut otherPointcut = (AbstractRegexpMethodPointcut) other;
207                return (Arrays.equals(this.patterns, otherPointcut.patterns) &&
208                                Arrays.equals(this.excludedPatterns, otherPointcut.excludedPatterns));
209        }
210
211        @Override
212        public int hashCode() {
213                int result = 27;
214                for (String pattern : this.patterns) {
215                        result = 13 * result + pattern.hashCode();
216                }
217                for (String excludedPattern : this.excludedPatterns) {
218                        result = 13 * result + excludedPattern.hashCode();
219                }
220                return result;
221        }
222
223        @Override
224        public String toString() {
225                return getClass().getName() + ": patterns " + ObjectUtils.nullSafeToString(this.patterns) +
226                                ", excluded patterns " + ObjectUtils.nullSafeToString(this.excludedPatterns);
227        }
228
229}