001    /*
002     * $Id: StackTraceScanner.java,v 1.5 2014/05/17 17:31:33 oboehm Exp $
003     *
004     * Copyright (c) 2014 by Oliver Boehm
005     *
006     * Licensed under the Apache License, Version 2.0 (the "License");
007     * you may not use this file except in compliance with the License.
008     * You may obtain a copy of the License at
009     *
010     *   http://www.apache.org/licenses/LICENSE-2.0
011     *
012     * Unless required by applicable law or agreed to in writing, software
013     * distributed under the License is distributed on an "AS IS" BASIS,
014     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express orimplied.
015     * See the License for the specific language governing permissions and
016     * limitations under the License.
017     *
018     * (c)reated 24.01.2014 by oliver (ob@oasd.de)
019     */
020    
021    package patterntesting.runtime.util;
022    
023    import java.lang.reflect.Method;
024    import java.util.regex.Pattern;
025    
026    import org.apache.commons.lang.ArrayUtils;
027    import org.aspectj.lang.Signature;
028    import org.aspectj.lang.reflect.*;
029    import org.slf4j.*;
030    
031    import patterntesting.runtime.exception.NotFoundException;
032    
033    /**
034     * This class allows you to scan the stacktrace for different stuff.
035     *
036     * @author oliver
037     * @since 1.4.1 (24.01.2014)
038     */
039    public final class StackTraceScanner {
040    
041        private static final Logger log = LoggerFactory.getLogger(StackTraceScanner.class);
042    
043        /** Utility class - no need to instantiate it. */
044        private StackTraceScanner() {}
045    
046        /**
047         * Find the constructor of the given class on the stack trace.
048         *
049         * @param clazz the clazz
050         * @return the stack trace element
051         */
052        public static StackTraceElement findConstructor(final Class<?> clazz) {
053            return find (clazz.getName(), "<init>");
054        }
055    
056        /**
057         * Find the given signature on the stack trace.
058         *
059         * @param signature the signature
060         * @return the stack trace element
061         */
062        public static StackTraceElement find(final Signature signature) {
063            if (signature instanceof MethodSignature) {
064                return find((MethodSignature) signature);
065            } else if (signature instanceof ConstructorSignature) {
066                return find((ConstructorSignature) signature);
067            } else {
068                throw new IllegalArgumentException(signature + " is not a method or ctor signature");
069            }
070        }
071    
072        private static StackTraceElement find(final ConstructorSignature signature) {
073            return findConstructor(signature.getDeclaringType());
074        }
075    
076        private static StackTraceElement find(final MethodSignature signature) {
077            return find(signature.getDeclaringType(), signature.getMethod());
078        }
079    
080        /**
081         * Find the given method on the stack trace.
082         *
083         * @param clazz the clazz
084         * @param method the method
085         * @return the stack trace element
086         */
087        public static StackTraceElement find(final Class<?> clazz, final Method method) {
088            return find (clazz.getName(), method.getName());
089        }
090    
091        /**
092         * Find the given method on the stack trace.
093         *
094         * @param clazz the clazz
095         * @param methodName the method name
096         * @return the stack trace element
097         */
098        public static StackTraceElement find(final Class<?> clazz, final String methodName) {
099            return find (clazz.getName(), methodName);
100        }
101    
102        /**
103         * Find the given method on the stack trace.
104         *
105         * @param classname the classname
106         * @param methodName the method name
107         * @return the stack trace element
108         */
109        public static StackTraceElement find(final String classname, final String methodName) {
110            StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
111            int i = getIndexOf(classname, methodName, stackTrace);
112            return stackTrace[i];
113        }
114    
115        /**
116         * Gets the caller of a constructor.
117         *
118         * @param clazz the clazz
119         * @return the caller of constructor
120         */
121        public static StackTraceElement getCallerOfConstructor(final Class<?> clazz) {
122            return getCallerOfConstructor(clazz, new Pattern[0]);
123        }
124    
125        /**
126         * Gets the caller of a constructor.
127         *
128         * @param clazz the clazz
129         * @param excluded the excluded
130         * @return the caller of constructor
131         */
132        public static StackTraceElement getCallerOfConstructor(final Class<?> clazz, final Pattern...excluded) {
133            try {
134                return getCallerOf(clazz, "<init>", excluded);
135            } catch (NotFoundException nfe) {
136                log.debug("Looking for adviced init method because of {}", nfe.getMessage());
137                StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
138                for (int i = 1; i < stackTrace.length; i++) {
139                    if (stackTrace[i].getMethodName().startsWith("init$")) {
140                        for (i++; i < stackTrace.length; i++) {
141                            if (!matches(stackTrace[i].getMethodName(), excluded)) {
142                                return stackTrace[i];
143                            }
144                        }
145                    }
146                }
147                throw new NotFoundException("new " + clazz.getSimpleName() + "(..) not part of " + stackTrace);
148            }
149        }
150    
151        /**
152         * Gets the caller of the given signature.
153         *
154         * @param signature the signature
155         * @return the caller of
156         */
157        public static StackTraceElement getCallerOf(final Signature signature) {
158            return getCallerOf(signature, new Pattern[0]);
159        }
160    
161        /**
162         * Gets the caller of the given method.
163         *
164         * @param clazz the clazz
165         * @param method the method
166         * @return the caller of
167         */
168        public static StackTraceElement getCallerOf(final Class<?> clazz, final Method method) {
169            return getCallerOf(clazz, method, new Pattern[0]);
170        }
171    
172        /**
173         * Gets the caller of the given method.
174         *
175         * @param clazz the clazz
176         * @param methodName the method name
177         * @return the caller of
178         */
179        public static StackTraceElement getCallerOf(final Class<?> clazz, final String methodName) {
180            return getCallerOf(clazz, methodName, new Pattern[0]);
181        }
182    
183        /**
184         * Gets the caller of the given method.
185         *
186         * @param classname the classname
187         * @param methodName the method name
188         * @return the caller of
189         */
190        public static StackTraceElement getCallerOf(final String classname, final String methodName) {
191            return getCallerOf(classname, methodName, new Pattern[0]);
192        }
193    
194        /**
195         * Gets the caller of the given signature.
196         *
197         * @param signature the signature
198         * @param excluded the excluded
199         * @return the caller of
200         */
201        public static StackTraceElement getCallerOf(final Signature signature, final Pattern... excluded) {
202            if (signature instanceof MethodSignature) {
203                return getCallerOf((MethodSignature) signature, excluded);
204            } else if (signature instanceof ConstructorSignature) {
205                return getCallerOf((ConstructorSignature) signature, excluded);
206            } else {
207                throw new IllegalArgumentException(signature + " is not a method or ctor signature");
208            }
209        }
210    
211        private static StackTraceElement getCallerOf(final ConstructorSignature signature,
212                final Pattern... excluded) {
213            return getCallerOfConstructor(signature.getDeclaringType(), excluded);
214        }
215    
216        private static StackTraceElement getCallerOf(final MethodSignature signature,
217                final Pattern... excluded) {
218            return getCallerOf(signature.getDeclaringType(), signature.getMethod(), excluded);
219        }
220    
221        /**
222         * Gets the caller of the given method.
223         *
224         * @param clazz the clazz
225         * @param method the method
226         * @param excluded a list of filters which should be not considered as
227         *        caller
228         * @return the caller of
229         */
230        public static StackTraceElement getCallerOf(final Class<?> clazz, final Method method, final Pattern... excluded) {
231            return getCallerOf(clazz.getName(), method.getName(), excluded);
232        }
233    
234        /**
235         * Gets the caller of the given method.
236         *
237         * @param clazz the clazz
238         * @param methodName the method name
239         * @param excluded a list of filters which should be not considered as
240         * caller
241         * @return the caller of
242         */
243        public static StackTraceElement getCallerOf(final Class<?> clazz, final String methodName, final Pattern... excluded) {
244            return getCallerOf(clazz.getName(), methodName, excluded);
245        }
246    
247        /**
248         * Gets the caller of the given method.
249         *
250         * @param classname the classname
251         * @param methodName the method name
252         * @param excluded a list of filters which should be not considered as
253         *        caller
254         * @return the caller of
255         */
256        public static StackTraceElement getCallerOf(final String classname, final String methodName, final Pattern... excluded) {
257            StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
258            for (int i = getIndexOf(classname, methodName, stackTrace) + 1; i < stackTrace.length; i++) {
259                if (!matches(stackTrace[i].getMethodName(), excluded)) {
260                    return stackTrace[i];
261                }
262            }
263            throw new NotFoundException(classname + "." + methodName +"()");
264        }
265    
266        private static int getIndexOf(final String classname, final String methodName,
267                final StackTraceElement[] stackTrace) {
268            for (int i = 1; i < stackTrace.length; i++) {
269                StackTraceElement element = stackTrace[i];
270                if (methodName.equals(element.getMethodName())
271                        && classname.equals(element.getClassName())) {
272                    return i;
273                }
274            }
275            throw new NotFoundException(classname + "." + methodName +"()");
276        }
277    
278        private static boolean matches(final String methodName, final Pattern... excluded) {
279            for (int j = 0; j < excluded.length; j++) {
280                if (excluded[j].matcher(methodName).matches()) {
281                    return true;
282                }
283            }
284            return false;
285        }
286    
287        private static boolean matches(final String className, final Class<?>... excluded) {
288            for (int j = 0; j < excluded.length; j++) {
289                if (className.equals(excluded[j].getName())) {
290                    return true;
291                }
292            }
293            return false;
294        }
295    
296        /**
297         * Gets the caller class by examing the stacktrace.
298         *
299         * @return the caller class
300         */
301        public static Class<?> getCallerClass() {
302            return getCallerClass(new Pattern[0]);
303        }
304    
305        /**
306         * Gets the caller class by examing the stacktrace.
307         *
308         * @param excluded a list of filters which should be not considered as
309         *        caller
310         * @return the caller of
311         */
312        public static Class<?> getCallerClass(final Pattern... excluded) {
313            return getCallerClass(excluded, new Class<?>[0]);
314        }
315    
316        /**
317         * Gets the caller class by examing the stacktrace.
318         *
319         * @param excludedMethods the excluded methods
320         * @param excludedClasses the excluded classes
321         * @return the caller class
322         */
323        public static Class<?> getCallerClass(final Pattern[] excludedMethods, final Class<?>...excludedClasses) {
324            StackTraceElement[] stackTrace = getCallerStackTrace(excludedMethods, excludedClasses);
325            String classname = stackTrace[0].getClassName();
326            try {
327                return Class.forName(classname);
328            } catch (ClassNotFoundException ex) {
329                throw new NotFoundException(classname, ex);
330            }
331        }
332    
333        /**
334         * Gets the caller stack trace of the method or constructor which calls it.
335         *
336         * @return the caller stack trace
337         * @since 1.4.2 (17.05.2014)
338         */
339        public static StackTraceElement[] getCallerStackTrace() {
340            return getCallerStackTrace(new Pattern[0]);
341        }
342    
343        /**
344         * Gets the caller stack trace of the method or constructor which calls it.
345         *
346         * @param excluded a list of filters which should be not considered as
347         *        caller
348         * @return the caller stack trace
349         * @since 1.4.2 (17.05.2014)
350         */
351        public static StackTraceElement[] getCallerStackTrace(final Pattern... excluded) {
352            return getCallerStackTrace(excluded, new Class<?>[0]);
353        }
354    
355        /**
356         * Gets the caller stack trace of the method or constructor which calls it.
357         *
358         * @param excludedMethods the excluded methods
359         * @param excludedClasses the excluded classes
360         * @return the caller stack trace
361         * @since 1.4.2 (17.05.2014)
362         */
363        public static StackTraceElement[] getCallerStackTrace(final Pattern[] excludedMethods, final Class<?>...excludedClasses) {
364            StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
365            int i = 2;
366            String scannerClassName = StackTraceScanner.class.getName();
367            for (; i < stackTrace.length-1; i++) {
368                if (!scannerClassName.equals(stackTrace[i].getClassName())) {
369                    break;
370                }
371            }
372            for (; i < stackTrace.length-1; i++) {
373                if (!matches(stackTrace[i].getMethodName(), excludedMethods)
374                        && !matches(stackTrace[i].getClassName(), excludedClasses)) {
375                    break;
376                }
377            }
378            //Arrays.copyOf(stackTrace, i, stackTrace.length);
379            return (StackTraceElement[]) ArrayUtils.subarray(stackTrace, i, stackTrace.length);
380        }
381    
382    }
383