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