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