001/*
002 * Copyright 2002-2019 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;
021
022import org.springframework.aop.ClassFilter;
023import org.springframework.aop.IntroductionAwareMethodMatcher;
024import org.springframework.aop.MethodMatcher;
025import org.springframework.lang.Nullable;
026import org.springframework.util.Assert;
027
028/**
029 * Static utility methods for composing {@link MethodMatcher MethodMatchers}.
030 *
031 * <p>A MethodMatcher may be evaluated statically (based on method and target
032 * class) or need further evaluation dynamically (based on arguments at the
033 * time of method invocation).
034 *
035 * @author Rod Johnson
036 * @author Rob Harrop
037 * @author Juergen Hoeller
038 * @author Sam Brannen
039 * @since 11.11.2003
040 * @see ClassFilters
041 * @see Pointcuts
042 */
043public abstract class MethodMatchers {
044
045        /**
046         * Match all methods that <i>either</i> (or both) of the given MethodMatchers matches.
047         * @param mm1 the first MethodMatcher
048         * @param mm2 the second MethodMatcher
049         * @return a distinct MethodMatcher that matches all methods that either
050         * of the given MethodMatchers matches
051         */
052        public static MethodMatcher union(MethodMatcher mm1, MethodMatcher mm2) {
053                return (mm1 instanceof IntroductionAwareMethodMatcher || mm2 instanceof IntroductionAwareMethodMatcher ?
054                                new UnionIntroductionAwareMethodMatcher(mm1, mm2) : new UnionMethodMatcher(mm1, mm2));
055        }
056
057        /**
058         * Match all methods that <i>either</i> (or both) of the given MethodMatchers matches.
059         * @param mm1 the first MethodMatcher
060         * @param cf1 the corresponding ClassFilter for the first MethodMatcher
061         * @param mm2 the second MethodMatcher
062         * @param cf2 the corresponding ClassFilter for the second MethodMatcher
063         * @return a distinct MethodMatcher that matches all methods that either
064         * of the given MethodMatchers matches
065         */
066        static MethodMatcher union(MethodMatcher mm1, ClassFilter cf1, MethodMatcher mm2, ClassFilter cf2) {
067                return (mm1 instanceof IntroductionAwareMethodMatcher || mm2 instanceof IntroductionAwareMethodMatcher ?
068                                new ClassFilterAwareUnionIntroductionAwareMethodMatcher(mm1, cf1, mm2, cf2) :
069                                new ClassFilterAwareUnionMethodMatcher(mm1, cf1, mm2, cf2));
070        }
071
072        /**
073         * Match all methods that <i>both</i> of the given MethodMatchers match.
074         * @param mm1 the first MethodMatcher
075         * @param mm2 the second MethodMatcher
076         * @return a distinct MethodMatcher that matches all methods that both
077         * of the given MethodMatchers match
078         */
079        public static MethodMatcher intersection(MethodMatcher mm1, MethodMatcher mm2) {
080                return (mm1 instanceof IntroductionAwareMethodMatcher || mm2 instanceof IntroductionAwareMethodMatcher ?
081                                new IntersectionIntroductionAwareMethodMatcher(mm1, mm2) : new IntersectionMethodMatcher(mm1, mm2));
082        }
083
084        /**
085         * Apply the given MethodMatcher to the given Method, supporting an
086         * {@link org.springframework.aop.IntroductionAwareMethodMatcher}
087         * (if applicable).
088         * @param mm the MethodMatcher to apply (may be an IntroductionAwareMethodMatcher)
089         * @param method the candidate method
090         * @param targetClass the target class
091         * @param hasIntroductions {@code true} if the object on whose behalf we are
092         * asking is the subject on one or more introductions; {@code false} otherwise
093         * @return whether or not this method matches statically
094         */
095        public static boolean matches(MethodMatcher mm, Method method, Class<?> targetClass, boolean hasIntroductions) {
096                Assert.notNull(mm, "MethodMatcher must not be null");
097                return (mm instanceof IntroductionAwareMethodMatcher ?
098                                ((IntroductionAwareMethodMatcher) mm).matches(method, targetClass, hasIntroductions) :
099                                mm.matches(method, targetClass));
100        }
101
102
103        /**
104         * MethodMatcher implementation for a union of two given MethodMatchers.
105         */
106        @SuppressWarnings("serial")
107        private static class UnionMethodMatcher implements MethodMatcher, Serializable {
108
109                protected final MethodMatcher mm1;
110
111                protected final MethodMatcher mm2;
112
113                public UnionMethodMatcher(MethodMatcher mm1, MethodMatcher mm2) {
114                        Assert.notNull(mm1, "First MethodMatcher must not be null");
115                        Assert.notNull(mm2, "Second MethodMatcher must not be null");
116                        this.mm1 = mm1;
117                        this.mm2 = mm2;
118                }
119
120                @Override
121                public boolean matches(Method method, Class<?> targetClass) {
122                        return (matchesClass1(targetClass) && this.mm1.matches(method, targetClass)) ||
123                                        (matchesClass2(targetClass) && this.mm2.matches(method, targetClass));
124                }
125
126                protected boolean matchesClass1(Class<?> targetClass) {
127                        return true;
128                }
129
130                protected boolean matchesClass2(Class<?> targetClass) {
131                        return true;
132                }
133
134                @Override
135                public boolean isRuntime() {
136                        return this.mm1.isRuntime() || this.mm2.isRuntime();
137                }
138
139                @Override
140                public boolean matches(Method method, Class<?> targetClass, Object... args) {
141                        return this.mm1.matches(method, targetClass, args) || this.mm2.matches(method, targetClass, args);
142                }
143
144                @Override
145                public boolean equals(@Nullable Object other) {
146                        if (this == other) {
147                                return true;
148                        }
149                        if (!(other instanceof UnionMethodMatcher)) {
150                                return false;
151                        }
152                        UnionMethodMatcher that = (UnionMethodMatcher) other;
153                        return (this.mm1.equals(that.mm1) && this.mm2.equals(that.mm2));
154                }
155
156                @Override
157                public int hashCode() {
158                        return 37 * this.mm1.hashCode() + this.mm2.hashCode();
159                }
160
161                @Override
162                public String toString() {
163                        return getClass().getName() + ": " + this.mm1 + ", " + this.mm2;
164                }
165        }
166
167
168        /**
169         * MethodMatcher implementation for a union of two given MethodMatchers
170         * of which at least one is an IntroductionAwareMethodMatcher.
171         * @since 5.1
172         */
173        @SuppressWarnings("serial")
174        private static class UnionIntroductionAwareMethodMatcher extends UnionMethodMatcher
175                        implements IntroductionAwareMethodMatcher {
176
177                public UnionIntroductionAwareMethodMatcher(MethodMatcher mm1, MethodMatcher mm2) {
178                        super(mm1, mm2);
179                }
180
181                @Override
182                public boolean matches(Method method, Class<?> targetClass, boolean hasIntroductions) {
183                        return (matchesClass1(targetClass) && MethodMatchers.matches(this.mm1, method, targetClass, hasIntroductions)) ||
184                                        (matchesClass2(targetClass) && MethodMatchers.matches(this.mm2, method, targetClass, hasIntroductions));
185                }
186        }
187
188
189        /**
190         * MethodMatcher implementation for a union of two given MethodMatchers,
191         * supporting an associated ClassFilter per MethodMatcher.
192         */
193        @SuppressWarnings("serial")
194        private static class ClassFilterAwareUnionMethodMatcher extends UnionMethodMatcher {
195
196                private final ClassFilter cf1;
197
198                private final ClassFilter cf2;
199
200                public ClassFilterAwareUnionMethodMatcher(MethodMatcher mm1, ClassFilter cf1, MethodMatcher mm2, ClassFilter cf2) {
201                        super(mm1, mm2);
202                        this.cf1 = cf1;
203                        this.cf2 = cf2;
204                }
205
206                @Override
207                protected boolean matchesClass1(Class<?> targetClass) {
208                        return this.cf1.matches(targetClass);
209                }
210
211                @Override
212                protected boolean matchesClass2(Class<?> targetClass) {
213                        return this.cf2.matches(targetClass);
214                }
215
216                @Override
217                public boolean equals(@Nullable Object other) {
218                        if (this == other) {
219                                return true;
220                        }
221                        if (!super.equals(other)) {
222                                return false;
223                        }
224                        ClassFilter otherCf1 = ClassFilter.TRUE;
225                        ClassFilter otherCf2 = ClassFilter.TRUE;
226                        if (other instanceof ClassFilterAwareUnionMethodMatcher) {
227                                ClassFilterAwareUnionMethodMatcher cfa = (ClassFilterAwareUnionMethodMatcher) other;
228                                otherCf1 = cfa.cf1;
229                                otherCf2 = cfa.cf2;
230                        }
231                        return (this.cf1.equals(otherCf1) && this.cf2.equals(otherCf2));
232                }
233
234                @Override
235                public int hashCode() {
236                        // Allow for matching with regular UnionMethodMatcher by providing same hash...
237                        return super.hashCode();
238                }
239
240                @Override
241                public String toString() {
242                        return getClass().getName() + ": " + this.cf1 + ", " + this.mm1 + ", " + this.cf2 + ", " + this.mm2;
243                }
244        }
245
246
247        /**
248         * MethodMatcher implementation for a union of two given MethodMatchers
249         * of which at least one is an IntroductionAwareMethodMatcher,
250         * supporting an associated ClassFilter per MethodMatcher.
251         * @since 5.1
252         */
253        @SuppressWarnings("serial")
254        private static class ClassFilterAwareUnionIntroductionAwareMethodMatcher extends ClassFilterAwareUnionMethodMatcher
255                        implements IntroductionAwareMethodMatcher {
256
257                public ClassFilterAwareUnionIntroductionAwareMethodMatcher(
258                                MethodMatcher mm1, ClassFilter cf1, MethodMatcher mm2, ClassFilter cf2) {
259
260                        super(mm1, cf1, mm2, cf2);
261                }
262
263                @Override
264                public boolean matches(Method method, Class<?> targetClass, boolean hasIntroductions) {
265                        return (matchesClass1(targetClass) && MethodMatchers.matches(this.mm1, method, targetClass, hasIntroductions)) ||
266                                        (matchesClass2(targetClass) && MethodMatchers.matches(this.mm2, method, targetClass, hasIntroductions));
267                }
268        }
269
270
271        /**
272         * MethodMatcher implementation for an intersection of two given MethodMatchers.
273         */
274        @SuppressWarnings("serial")
275        private static class IntersectionMethodMatcher implements MethodMatcher, Serializable {
276
277                protected final MethodMatcher mm1;
278
279                protected final MethodMatcher mm2;
280
281                public IntersectionMethodMatcher(MethodMatcher mm1, MethodMatcher mm2) {
282                        Assert.notNull(mm1, "First MethodMatcher must not be null");
283                        Assert.notNull(mm2, "Second MethodMatcher must not be null");
284                        this.mm1 = mm1;
285                        this.mm2 = mm2;
286                }
287
288                @Override
289                public boolean matches(Method method, Class<?> targetClass) {
290                        return (this.mm1.matches(method, targetClass) && this.mm2.matches(method, targetClass));
291                }
292
293                @Override
294                public boolean isRuntime() {
295                        return (this.mm1.isRuntime() || this.mm2.isRuntime());
296                }
297
298                @Override
299                public boolean matches(Method method, Class<?> targetClass, Object... args) {
300                        // Because a dynamic intersection may be composed of a static and dynamic part,
301                        // we must avoid calling the 3-arg matches method on a dynamic matcher, as
302                        // it will probably be an unsupported operation.
303                        boolean aMatches = (this.mm1.isRuntime() ?
304                                        this.mm1.matches(method, targetClass, args) : this.mm1.matches(method, targetClass));
305                        boolean bMatches = (this.mm2.isRuntime() ?
306                                        this.mm2.matches(method, targetClass, args) : this.mm2.matches(method, targetClass));
307                        return aMatches && bMatches;
308                }
309
310                @Override
311                public boolean equals(@Nullable Object other) {
312                        if (this == other) {
313                                return true;
314                        }
315                        if (!(other instanceof IntersectionMethodMatcher)) {
316                                return false;
317                        }
318                        IntersectionMethodMatcher that = (IntersectionMethodMatcher) other;
319                        return (this.mm1.equals(that.mm1) && this.mm2.equals(that.mm2));
320                }
321
322                @Override
323                public int hashCode() {
324                        return 37 * this.mm1.hashCode() + this.mm2.hashCode();
325                }
326
327                @Override
328                public String toString() {
329                        return getClass().getName() + ": " + this.mm1 + ", " + this.mm2;
330                }
331        }
332
333
334        /**
335         * MethodMatcher implementation for an intersection of two given MethodMatchers
336         * of which at least one is an IntroductionAwareMethodMatcher.
337         * @since 5.1
338         */
339        @SuppressWarnings("serial")
340        private static class IntersectionIntroductionAwareMethodMatcher extends IntersectionMethodMatcher
341                        implements IntroductionAwareMethodMatcher {
342
343                public IntersectionIntroductionAwareMethodMatcher(MethodMatcher mm1, MethodMatcher mm2) {
344                        super(mm1, mm2);
345                }
346
347                @Override
348                public boolean matches(Method method, Class<?> targetClass, boolean hasIntroductions) {
349                        return (MethodMatchers.matches(this.mm1, method, targetClass, hasIntroductions) &&
350                                        MethodMatchers.matches(this.mm2, method, targetClass, hasIntroductions));
351                }
352        }
353
354}