001    /*
002     * $Id: JoinPointHelper.java,v 1.19 2014/02/07 18:29:58 oboehm Exp $
003     *
004     * Copyright (c) 2008 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 23.10.2008 by oliver (ob@oasd.de)
019     */
020    package patterntesting.runtime.util;
021    
022    import java.lang.annotation.Annotation;
023    import java.lang.reflect.*;
024    import java.util.regex.Pattern;
025    
026    import org.aspectj.lang.*;
027    import org.aspectj.lang.reflect.*;
028    
029    import patterntesting.annotation.check.runtime.*;
030    import patterntesting.runtime.annotation.DontLogMe;
031    
032    /**
033     * The Class JoinPointHelper.
034     *
035     * @author <a href="boehm@javatux.de">oliver</a>
036     * @since 23.10.2008
037     * @version $Revision: 1.19 $
038     */
039    public final class JoinPointHelper {
040    
041        private static Pattern[] ADVICE_PATTERNS = {
042            Pattern.compile("ajc\\$.*"),
043            Pattern.compile(".*\\$_aroundBody.*")
044        };
045    
046        /** Utility class - no need to instantiate it */
047        private JoinPointHelper() {}
048    
049        /**
050         * Gets the given joinpoint as string.
051         *
052         * @param joinpoint the joinpoint
053         *
054         * @return the as string
055         */
056        public static String getAsString(final JoinPoint joinpoint) {
057            Signature sig = joinpoint.getSignature();
058            Object[] args = joinpoint.getArgs();
059            if (sig instanceof FieldSignature) {
060                if (args.length == 0) {
061                    return "get " + sig.toShortString();
062                } else {
063                    return "set " + sig.toShortString() + " = "
064                            + getArgAsString(args[0]);
065                }
066            } else if (sig instanceof CodeSignature) {
067                Annotation[][] paramAnnotations = getParameterAnnotations(joinpoint);
068                return SignatureHelper.getAsString(sig.getDeclaringTypeName(), sig)
069                        + getArgsAsString(args, paramAnnotations);
070            } else {
071                return SignatureHelper.getAsString(sig.getDeclaringTypeName(), sig)
072                        + getArgsAsString(args);
073            }
074        }
075    
076        /**
077         * Gets the as short string.
078         *
079         * @param joinpoint the joinpoint
080         *
081         * @return the as short string
082         */
083        public static String getAsShortString(final JoinPoint joinpoint) {
084            Signature sig = joinpoint.getSignature();
085            if (sig instanceof FieldSignature) {
086                return getAsString(joinpoint);
087            }
088            Object[] args = joinpoint.getArgs();
089            if (sig instanceof CatchClauseSignature) {
090                return SignatureHelper.getAsString(sig.getDeclaringType().getSimpleName(), sig) + "("
091                        + args[0].getClass().getName() + ")";
092            }
093            return SignatureHelper.getAsString(sig.getDeclaringType().getSimpleName(), sig)
094                    + getArgsAsShortString(args);
095        }
096    
097        /**
098         * Gets the as long string.
099         *
100         * @param joinpoint the joinpoint
101         *
102         * @return the as short string
103         */
104        public static String getAsLongString(final JoinPoint joinpoint) {
105            Signature sig = joinpoint.getSignature();
106            if (sig instanceof FieldSignature) {
107                return getAsString(joinpoint);
108            }
109            Object[] args = joinpoint.getArgs();
110            return SignatureHelper.getAsString(sig.getDeclaringType().getName(), sig)
111                    + getArgsAsString(args);
112        }
113    
114        /**
115         * Gets the parameter annotations.
116         *
117         * @param joinpoint should be a Method- or Constructor-Signature
118         *
119         * @return the annotations of a method or constructor, otherwise null
120         */
121        @MayReturnNull
122        public static Annotation[][] getParameterAnnotations(final JoinPoint joinpoint) {
123            Signature sig = joinpoint.getSignature();
124            if (sig instanceof MethodSignature) {
125                Method method = ((MethodSignature) sig).getMethod();
126                if (method == null) {
127                    return null;
128                }
129                return method.getParameterAnnotations();
130            } else if (sig instanceof ConstructorSignature) {
131                ConstructorSignature ctorSig = (ConstructorSignature) sig;
132                return getParameterAnnotations(ctorSig);
133            } else {
134                return null;
135            }
136        }
137    
138        /**
139         * Normally you get an annotation array with the size of the constructor
140         * parameters. But not for inner classes. Here the first parameter of the
141         * constructor is the embedded class. In this case we must correct the
142         * annotation array
143         *
144         * @param ctorSig
145         * @return
146         */
147        @MayReturnNull
148        private static Annotation[][] getParameterAnnotations(final ConstructorSignature ctorSig) {
149            Constructor<?> ctor = ctorSig.getConstructor();
150            Annotation[][] annotations = ctor.getParameterAnnotations();
151            if (isInnerClass(ctorSig)) {
152                Annotation[][] corrected = new Annotation[annotations.length+1][0];
153                corrected[0] = null;
154                System.arraycopy(annotations, 0, corrected, 1, annotations.length);
155                return corrected;
156            }
157            return annotations;
158        }
159    
160        /**
161         * Returns the annotation for the given JoinPoint.
162         *
163         * @since 1.0
164         * @param jp the JoinPoint
165         * @param annotationClass the wanted annotation
166         * @return the annotation (if found) or null
167         */
168        @MayReturnNull
169        public static Annotation getClassAnnotation(final JoinPoint jp,
170                final Class<? extends Annotation> annotationClass) {
171            Class<?> thisClass = jp.getSignature().getDeclaringType();
172            assert thisClass != null;
173            return thisClass.getAnnotation(annotationClass);
174        }
175    
176            /**
177             * Gets the args as string.
178             *
179             * @param args the args
180             *
181             * @return the args as string
182             */
183            @NullArgsAllowed
184        public static String getArgsAsString(final Object[] args) {
185            if ((args == null) || (args.length == 0)) {
186                return "()";
187            }
188            StringBuilder sb = new StringBuilder("(");
189            sb.append(getArgAsString(args[0]));
190            for (int i = 1; i < args.length; i++) {
191                sb.append(", ");
192                sb.append(getArgAsString(args[i]));
193            }
194            sb.append(")");
195            return sb.toString();
196        }
197    
198            @NullArgsAllowed
199        private static String getArgsAsString(final Object[] args,
200                final Annotation[][] annotations) {
201            if ((args == null) || (args.length == 0)) {
202                return "()";
203            }
204            StringBuilder sb = new StringBuilder("(");
205            sb.append(getArgAsString(args[0], annotations[0]));
206            for (int i = 1; i < args.length; i++) {
207                sb.append(", ");
208                sb.append(getArgAsString(args[i], annotations[i]));
209            }
210            sb.append(")");
211            return sb.toString();
212        }
213    
214            /**
215             * Gets the args as short string. If the resulting argument string will
216             * be too long it will be abbreviated.
217             *
218             * @param args the args
219             * @return the args as short string
220             * @since 1.4.1
221             */
222            @NullArgsAllowed
223        public static String getArgsAsShortString(final Object[] args) {
224                String shortString = getArgsAsString(args);
225                if (shortString.length() < 20) {
226                    return shortString;
227                }
228            StringBuilder sb = new StringBuilder("(");
229            sb.append(getArgAsShortString(args[0]));
230            int n = args.length;
231            if (n > 3) {
232                n = 3;
233            }
234            for (int i = 1; i < n; i++) {
235                sb.append(", ");
236                sb.append(getArgAsShortString(args[i]));
237            }
238            if (args.length > 3) {
239                sb.append(", ..");
240            }
241            sb.append(")");
242            return sb.toString();
243        }
244    
245        /**
246         * Gets the arg as string.
247         *
248         * @param obj the obj
249         *
250         * @return the arg as string
251         */
252        @NullArgsAllowed
253        public static String getArgAsString(final Object obj) {
254            if (obj == null) {
255                    return "(null)";
256            }
257            if (obj instanceof String) {
258                    return "\"" + obj + "\"";
259            }
260            return obj.toString();
261        }
262    
263        /**
264         * Gets the arg as short string.
265         *
266         * @param obj the obj
267         * @return the arg as short string
268         * @since 1.4.1
269         */
270        @NullArgsAllowed
271        public static String getArgAsShortString(final Object obj) {
272            if (obj == null) {
273                return "(null)";
274            }
275            String shortString = Converter.toShortString(obj);
276            if (obj instanceof String) {
277                return "\"" + shortString + "\"";
278            }
279            return shortString;
280        }
281    
282        @NullArgsAllowed
283        private static String getArgAsString(final Object obj,
284                final Annotation[] annotations) {
285            if (hasDontLogMeAnnotation(annotations)) {
286                return "*";
287            } else {
288                return getArgAsString(obj);
289            }
290        }
291    
292        private static boolean hasDontLogMeAnnotation(final Annotation[] annotations) {
293            if (annotations == null) {
294                return false;
295            }
296            for (Annotation annotation : annotations) {
297                if (annotation instanceof DontLogMe) {
298                    return true;
299                }
300            }
301            return false;
302        }
303    
304        /**
305         * The Java compiler generates something like OuterClass$InnerClass
306         * as name for the inner class. So if a name contains a '$' it is
307         * probably an inner class.
308         *
309         * @param jp the jp
310         *
311         * @return true if joinpoint belongs to an inner class
312         */
313        public static boolean isInnerClass(final JoinPoint jp) {
314            return isInnerClass(jp.getSignature());
315        }
316    
317        /**
318         * The Java compiler generates something like OuterClass$InnerClass
319         * as name for the inner class. So if a name contains a '$' it is
320         * probably an inner class.
321         *
322         * @param sig the sig
323         *
324         * @return true if signature belongs to an inner class
325         */
326        public static boolean isInnerClass(final Signature sig) {
327            String typeName = sig.getDeclaringTypeName();
328            return typeName.contains("$");
329        }
330    
331        /**
332         * Gets the caller class by examing the stacktrace.
333         * <p>
334         * Sometime the caller of the method is an aspect. Because normally you
335         * do not want the aspect it is filtered out. If you really want it call
336         * {@link #getCallerOf(JoinPoint, Pattern...)} direct with an empty pattern
337         * argument.
338         * </p>
339         *
340         * @return the caller of a joinpoint
341         * @see #getCallerOf(JoinPoint, Pattern...)
342         */
343        public static Class<?> getCallerClass() {
344            return StackTraceScanner.getCallerClass(ADVICE_PATTERNS, JoinPointHelper.class);
345        }
346    
347        /**
348         * Gets the caller class by examing the stacktrace.
349         * <p>
350         * Sometime the caller of the method is an aspect. With this method you
351         * have the chance to list the aspects which should be ignored.
352         * </p>
353         *
354         * @param excluded the excluded
355         * @return the caller of a joinpoint
356         */
357        public static Class<?> getCallerClass(final Class<?>... excluded) {
358            return StackTraceScanner.getCallerClass(ADVICE_PATTERNS, excluded);
359        }
360    
361        /**
362         * Gets the caller class by examing the stacktrace.
363         *
364         * @param excluded a list of filters which should be not considered as
365         *        caller
366         * @return the caller of
367         * @deprecated Use {@link StackTraceScanner#getCallerClass(Pattern...)} instead
368         */
369        @Deprecated
370        public static Class<?> getCallerClass(final Pattern... excluded) {
371            return StackTraceScanner.getCallerClass(excluded);
372        }
373    
374        /**
375         * Gets the caller of the given joinpoint. For a call joinpoint you have
376         * "this" as attribute which gives you the caller. But for an execution
377         * joinpoint "this" is not the caller but the object of execution. If you
378         * want the caller of the excution joinpoint call this method here.
379         * <p>
380         * Sometime the caller of the method is an aspect. Because normally you
381         * do not want the aspect it is filtered out. If you really want it call
382         * {@link #getCallerOf(JoinPoint, Pattern...)} direct with an empty pattern
383         * argument.
384         * </p>
385         *
386         * @param jp the (execution) joinpoint
387         * @return the caller of the given joinpoint
388         * @see #getCallerOf(JoinPoint, Pattern...)
389         */
390        public static StackTraceElement getCallerOf(final JoinPoint jp) {
391            return getCallerOf(jp, ADVICE_PATTERNS);
392        }
393    
394        /**
395         * Gets the caller of the given joinpoint. For a call joinpoint you have
396         * "this" as attribute which gives you the caller. But for an execution
397         * joinpoint "this" is not the caller but the object of execution. If you
398         * want the caller of the excution joinpoint call this method here.
399         *
400         * @param jp the (execution) joinpoint
401         * @param excluded a list of filters which should be not considered as
402         *        caller
403         * @return the caller of
404         */
405        public static StackTraceElement getCallerOf(final JoinPoint jp, final Pattern... excluded) {
406            return StackTraceScanner.getCallerOf(jp.getSignature(), excluded);
407        }
408    
409    }