View Javadoc

1   /**
2    * Copyright (c) 2012, University of Konstanz, Distributed Systems Group
3    * All rights reserved.
4    * 
5    * Redistribution and use in source and binary forms, with or without
6    * modification, are permitted provided that the following conditions are met:
7    * * Redistributions of source code must retain the above copyright
8    * notice, this list of conditions and the following disclaimer.
9    * * Redistributions in binary form must reproduce the above copyright
10   * notice, this list of conditions and the following disclaimer in the
11   * documentation and/or other materials provided with the distribution.
12   * * Neither the name of the University of Konstanz nor the
13   * names of its contributors may be used to endorse or promote products
14   * derived from this software without specific prior written permission.
15   * 
16   * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
17   * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18   * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
19   * DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
20   * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
21   * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
22   * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
23   * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24   * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
25   * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26   */
27  package org.perfidix.element;
28  
29  import java.lang.annotation.Annotation;
30  import java.lang.reflect.Method;
31  import java.lang.reflect.Modifier;
32  import java.util.ArrayList;
33  import java.util.List;
34  
35  import org.perfidix.annotation.AfterBenchClass;
36  import org.perfidix.annotation.AfterEachRun;
37  import org.perfidix.annotation.AfterLastRun;
38  import org.perfidix.annotation.BeforeBenchClass;
39  import org.perfidix.annotation.BeforeEachRun;
40  import org.perfidix.annotation.BeforeFirstRun;
41  import org.perfidix.annotation.Bench;
42  import org.perfidix.annotation.BenchClass;
43  import org.perfidix.annotation.SkipBench;
44  import org.perfidix.exceptions.PerfidixMethodCheckException;
45  
46  /**
47   * Class to mark one method which are possible benchmarkable. The method hold
48   * helping methods and additional functionality for benchmarkable methods like
49   * returning possible {@link BeforeBenchClass}, {@link BeforeFirstRun}, {@link BeforeEachRun},
50   * {@link AfterEachRun}, {@link AfterLastRun} and {@link AfterBenchClass} annotated related methods.
51   * 
52   * @see AfterBenchClass
53   * @see AfterLastRun
54   * @see AfterEachRun
55   * @see BeforeEachRun
56   * @see BeforeFirstRun
57   * @see BeforeBenchClass
58   * @author Sebastian Graf, University of Konstanz
59   */
60  public final class BenchmarkMethod {
61  
62      /**
63       * Method to be benched.
64       */
65      private transient final Method methodToBench;
66  
67      /**
68       * Constructor, with a definite method to bench. The method has to be
69       * checked with {@link BenchmarkMethod#isBenchmarkable(Method)} first,
70       * otherwise an IllegalArgumentException could arise.
71       * 
72       * @param paramMethod
73       *            method to be benched (eventually)
74       */
75      public BenchmarkMethod(final Method paramMethod) {
76          methodToBench = paramMethod;
77          if (!isBenchmarkable(methodToBench)) {
78              throw new IllegalArgumentException(new StringBuilder(
79                  "Only benchmarkable methods allowed but method ").append(paramMethod).append(
80                  " is not benchmarkable.").toString());
81          }
82      }
83  
84      /**
85       * Method to find a {@link BeforeFirstRun} annotation. This method should be
86       * invoked for all methods. The corresponding class is searched after
87       * suitable methods and checks for integrity are made. If there are multiple {@link BeforeFirstRun}
88       * -annotated methods available, an exception is
89       * thrown. If there are designated special {@link BeforeFirstRun} methods as
90       * given in the parameter of the {@link Bench}-annotation, this method is
91       * taken with any further checking of the other methods in the class.
92       * 
93       * @see BeforeFirstRun
94       * @see Bench
95       * @return Annotated method with BeforeFirstRun annotation, null of none
96       *         exists
97       * @throws PerfidixMethodCheckException
98       *             if integrity check of class and method fails.
99       */
100     public Method[] findBeforeFirstRun() throws PerfidixMethodCheckException {
101 
102         Method method = null;
103 
104         final Bench benchAnno = getMethodToBench().getAnnotation(Bench.class);
105         if (benchAnno != null && !benchAnno.beforeFirstRun().equals("")) {
106             List<Method> returnval = new ArrayList<Method>();
107 
108             try {
109                 // variable to instantiate the method by name.
110                 final Class<?>[] setUpParams = {};
111 
112                 String[] methods = benchAnno.beforeFirstRun().split(",");
113                 for (String methodString : methods) {
114                     // getting the method by name
115                     method =
116                         getMethodToBench().getDeclaringClass().getDeclaredMethod(methodString.trim(), setUpParams);
117 
118                     if (isReflectedExecutable(method, BeforeFirstRun.class)) {
119                         returnval.add(method);
120                     } else {
121                         throw new PerfidixMethodCheckException(new IllegalAccessException(new StringBuilder(
122                             "Failed to execute BeforeFirstRun-annotated method ").append(method).toString()),
123                             method, BeforeFirstRun.class);
124                     }
125                 }
126                 return returnval.toArray(new Method[returnval.size()]);
127 
128             } catch (final SecurityException e) {
129                 throw new PerfidixMethodCheckException(e, method, BeforeFirstRun.class);
130             } catch (final NoSuchMethodException e) {
131                 throw new PerfidixMethodCheckException(e, method, BeforeFirstRun.class);
132             }
133         }
134 
135         // if there was no name, a scan over the class occurs, otherwise the
136         // designated method is checked.
137 
138         else {
139             Method meth =
140                 findAndCheckAnyMethodByAnnotation(getMethodToBench().getDeclaringClass(),
141                     BeforeFirstRun.class);
142             if (meth == null) {
143                 return new Method[0];
144             } else {
145                 return new Method[] {
146                     meth
147                 };
148             }
149         }
150 
151     }
152 
153     /**
154      * Method to find a {@link BeforeEachRun} annotation. This method should be
155      * invoked for all methods. The corresponding class is searched after
156      * suitable methods and checks for integrity are made. If there are multiple {@link BeforeEachRun}
157      * -annotated methods available, an exception is
158      * thrown. If there are designated special {@link BeforeEachRun} methods as
159      * given in the parameter of the {@link Bench}-annotation, this method is
160      * taken with any further checking of the other methods in the class.
161      * 
162      * @see BeforeEachRun
163      * @see Bench
164      * @return Annotated method with BeforeEachRun annotation, null of none
165      *         exists
166      * @throws PerfidixMethodCheckException
167      *             if integrity check of class and method fails.
168      */
169     public Method[] findBeforeEachRun() throws PerfidixMethodCheckException {
170 
171         Method method = null;
172 
173         final Bench benchAnno = getMethodToBench().getAnnotation(Bench.class);
174         if (benchAnno != null && !benchAnno.beforeEachRun().equals("")) {
175             List<Method> returnval = new ArrayList<Method>();
176             try {
177                 // variable to instantiate the method by name.
178                 final Class<?>[] setUpParams = {};
179 
180                 String[] methods = benchAnno.beforeEachRun().split(",");
181                 for (String methodString : methods) {
182 
183                     // getting the method by name
184                     method =
185                         getMethodToBench().getDeclaringClass().getDeclaredMethod(methodString.trim(), setUpParams);
186 
187                     if (isReflectedExecutable(method, BeforeEachRun.class)) {
188                         returnval.add(method);
189                     } else {
190                         throw new PerfidixMethodCheckException(new IllegalAccessException(new StringBuilder(
191                             " Failed to execute BeforeEachRun-annotated method ").append(method).toString()),
192                             method, BeforeEachRun.class);
193                     }
194                 }
195                 return returnval.toArray(new Method[returnval.size()]);
196 
197             } catch (SecurityException e) {
198                 throw new PerfidixMethodCheckException(e, method, BeforeEachRun.class);
199             } catch (NoSuchMethodException e) {
200                 throw new PerfidixMethodCheckException(e, method, BeforeEachRun.class);
201             }
202         } else {
203 
204             // if there was no name, a scan over the class occurs, otherwise the
205             // designated method is checked.
206             Method meth =
207                 findAndCheckAnyMethodByAnnotation(getMethodToBench().getDeclaringClass(), BeforeEachRun.class);
208             if (meth == null) {
209                 return new Method[0];
210             } else {
211                 return new Method[] {
212                     meth
213                 };
214             }
215 
216         }
217     }
218 
219     /**
220      * Method to find a {@link AfterEachRun} annotation. This method should be
221      * invoked for all methods. The corresponding class is searched after
222      * suitable methods and checks for integrity are made. If there are multiple {@link AfterEachRun}
223      * -annotated methods available, an exception is
224      * thrown. If there are designated special {@link AfterEachRun} methods as
225      * given in the parameter of the {@link Bench}-annotation, this method is
226      * taken with any further checking of the other methods in the class.
227      * 
228      * @see AfterEachRun
229      * @see Bench
230      * @return Annotated method with AfterEachRun annotation, null of none
231      *         exists
232      * @throws PerfidixMethodCheckException
233      *             if integrity check of class and method fails.
234      */
235     public Method[] findAfterEachRun() throws PerfidixMethodCheckException {
236 
237         Method method = null;
238 
239         final Bench benchAnno = getMethodToBench().getAnnotation(Bench.class);
240         if (benchAnno != null && !benchAnno.afterEachRun().equals("")) {
241             List<Method> returnval = new ArrayList<Method>();
242             try {
243                 // variable to instantiate the method by name.
244                 final Class<?>[] setUpParams = {};
245 
246                 String[] methods = benchAnno.afterEachRun().split(",");
247                 for (String methodString : methods) {
248                     // getting the method by name
249                     method =
250                         getMethodToBench().getDeclaringClass().getDeclaredMethod(methodString.trim(), setUpParams);
251                     if (isReflectedExecutable(method, AfterEachRun.class)) {
252                         returnval.add(method);
253                     } else {
254                         throw new PerfidixMethodCheckException(new IllegalAccessException(new StringBuilder(
255                             "AfterEachRun-annotated method ").append(method).append(" is not executable.")
256                             .toString()), method, AfterEachRun.class);
257                     }
258                 }
259                 return returnval.toArray(new Method[returnval.size()]);
260             } catch (SecurityException e) {
261                 throw new PerfidixMethodCheckException(e, method, AfterEachRun.class);
262             } catch (NoSuchMethodException e) {
263                 throw new PerfidixMethodCheckException(e, method, AfterEachRun.class);
264             }
265         } else {
266             // if there was no name, a scan over the class occurs, otherwise the
267             // designated method is checked.
268             Method meth =
269                 findAndCheckAnyMethodByAnnotation(getMethodToBench().getDeclaringClass(), AfterEachRun.class);
270             if (meth == null) {
271                 return new Method[0];
272             } else {
273                 return new Method[] {
274                     meth
275                 };
276             }
277 
278         }
279     }
280 
281     /**
282      * Method to find a {@link AfterLastRun} annotation. This method should be
283      * invoked for all methods. The corresponding class is searched after
284      * suitable methods and checks for integrity are made. If there are multiple {@link AfterLastRun}
285      * -annotated methods available, an exception is
286      * thrown. If there are designated special {@link AfterLastRun} methods as
287      * given in the parameter of the {@link Bench}-annotation, this method is
288      * taken with any further checking of the other methods in the class.
289      * 
290      * @see AfterLastRun
291      * @see Bench
292      * @return Annotated method with AfterLastRun annotation, null of none
293      *         exists
294      * @throws PerfidixMethodCheckException
295      *             if integrity check of class and method fails.
296      */
297     public Method[] findAfterLastRun() throws PerfidixMethodCheckException {
298 
299         Method method = null;
300 
301         final Bench benchAnno = getMethodToBench().getAnnotation(Bench.class);
302         if (benchAnno != null && !benchAnno.afterLastRun().equals("")) {
303             List<Method> returnval = new ArrayList<Method>();
304             try {
305                 // variable to instantiate the method by name.
306                 final Class<?>[] setUpParams = {};
307 
308                 String[] methods = benchAnno.afterLastRun().split(",");
309                 for (String methodString : methods) {
310                     // getting the method by name
311                     method =
312                         getMethodToBench().getDeclaringClass().getDeclaredMethod(methodString.trim(), setUpParams);
313 
314                     if (isReflectedExecutable(method, AfterLastRun.class)) {
315                         returnval.add(method);
316                     } else {
317                         throw new PerfidixMethodCheckException(new IllegalAccessException(new StringBuilder(
318                             "AfterLastRun-annotated method ").append(method).append(" is not executable.")
319                             .toString()), method, AfterLastRun.class);
320                     }
321                 }
322                 return returnval.toArray(new Method[returnval.size()]);
323             } catch (final SecurityException e) {
324                 throw new PerfidixMethodCheckException(e, method, AfterLastRun.class);
325             } catch (final NoSuchMethodException e) {
326                 throw new PerfidixMethodCheckException(e, method, AfterLastRun.class);
327             }
328         } else {
329             // if there was no name, a scan over the class occurs, otherwise the
330             // designated method is checked.
331             Method meth =
332                 findAndCheckAnyMethodByAnnotation(getMethodToBench().getDeclaringClass(), AfterLastRun.class);
333             if (meth == null) {
334                 return new Method[0];
335             } else {
336                 return new Method[] {
337                     meth
338                 };
339             }
340         }
341     }
342 
343     /**
344      * Simple getter for encapsulated method.
345      * 
346      * @return the methodToBench
347      */
348     public Method getMethodToBench() {
349         return methodToBench;
350     }
351 
352     /**
353      * Getting the number of runs corresponding to a given method. The method
354      * MUST be a benchmarkable method, otherwise an IllegalStateException
355      * exception arises. The number of runs of an annotated method is more
356      * powerful than the number of runs as denoted by the benchclass annotation.
357      * 
358      * @param meth
359      *            to be checked
360      * @return the number of runs of this benchmarkable-method
361      */
362     public static int getNumberOfAnnotatedRuns(final Method meth) {
363         if (!isBenchmarkable(meth)) {
364             throw new IllegalArgumentException(new StringBuilder("Method ").append(meth).append(
365                 " must be a benchmarkable method.").toString());
366         }
367         final Bench benchAnno = meth.getAnnotation(Bench.class);
368         final BenchClass benchClassAnno = meth.getDeclaringClass().getAnnotation(BenchClass.class);
369         int returnVal;
370         if (benchAnno == null) {
371             returnVal = benchClassAnno.runs();
372         } else {
373             returnVal = benchAnno.runs();
374             // use runs from @BenchClass if none is set on method (issue #4)
375             if((returnVal == Bench.NONE_RUN) && (benchClassAnno != null)) {
376                 returnVal = benchClassAnno.runs();
377             }
378         }
379         return returnVal;
380     }
381 
382     /**
383      * This class finds any method with a given annotation. The method is
384      * allowed to occure only once in the class and should match the
385      * requirements for Perfidix for an execution by reflection.
386      * 
387      * @param anno
388      *            of the method to be found
389      * @param clazz
390      *            class to be searched
391      * @return a method annotated by the annotation given. The method occurs
392      *         only once in the class and matched the requirements of
393      *         perfidix-reflective-invocation.
394      * @throws PerfidixMethodCheckException
395      *             if these integrity checks fail
396      */
397     public static Method findAndCheckAnyMethodByAnnotation(final Class<?> clazz,
398         final Class<? extends Annotation> anno) throws PerfidixMethodCheckException {
399         // needed variables, one for check for duplicates
400         Method anyMethod = null;
401 
402         // Scanning all methods
403         final Method[] possAnnoMethods = clazz.getDeclaredMethods();
404         for (final Method meth : possAnnoMethods) {
405             if (meth.getAnnotation(anno) != null) {
406                 // Check if there are multiple annotated methods, throwing
407                 // IllegalAccessException otherwise.
408                 if (anyMethod == null) {
409                     // Check if method is valid (no param, no returnval,
410                     // etc.), throwing IllegalAccessException otherwise.
411                     if (isReflectedExecutable(meth, anno)) {
412                         anyMethod = meth;
413                     } else {
414                         throw new PerfidixMethodCheckException(new IllegalAccessException(new StringBuilder(
415                             anno.toString()).append("-annotated method ").append(meth).append(
416                             " is not executable.").toString()), meth, anno);
417                     }
418                 } else {
419                     throw new PerfidixMethodCheckException(new IllegalAccessException(new StringBuilder(
420                         "Please use only one ").append(anno.toString()).append("-annotation in one class.")
421                         .toString()), meth, anno);
422                 }
423             }
424         }
425 
426         return anyMethod;
427     }
428 
429     /**
430      * This method should act as a check to guarantee that only specific
431      * Benchmarkables are used for benching.
432      * 
433      * @param meth
434      *            method to be checked.
435      * @return true if an instance of this interface is benchmarkable, false
436      *         otherwise.
437      */
438     public static boolean isBenchmarkable(final Method meth) {
439         boolean returnVal = true;
440 
441         // Check if bench-anno is given. For testing purposes against
442         // before/after annos
443         final Bench benchAnno = meth.getAnnotation(Bench.class);
444 
445         // if method is annotated with SkipBench, the method is never
446         // benchmarkable.
447         final SkipBench skipBenchAnno = meth.getAnnotation(SkipBench.class);
448         if (skipBenchAnno != null) {
449             returnVal = false;
450         }
451 
452         // Check if method is defined as beforeClass, beforeFirstRun,
453         // beforeEachRun, afterEachRun, afterLastRun, afterClass. A method can
454         // either be a before/after class or afterwards be benchmarkable through
455         // the BenchClass annotation.
456         final BeforeBenchClass beforeClass = meth.getAnnotation(BeforeBenchClass.class);
457         if (beforeClass != null && benchAnno == null) {
458             returnVal = false;
459         }
460 
461         final BeforeFirstRun beforeFirstRun = meth.getAnnotation(BeforeFirstRun.class);
462         if (beforeFirstRun != null && benchAnno == null) {
463             returnVal = false;
464         }
465 
466         final BeforeEachRun beforeEachRun = meth.getAnnotation(BeforeEachRun.class);
467         if (beforeEachRun != null && benchAnno == null) {
468             returnVal = false;
469         }
470 
471         final AfterEachRun afterEachRun = meth.getAnnotation(AfterEachRun.class);
472         if (afterEachRun != null && benchAnno == null) {
473             returnVal = false;
474         }
475 
476         final AfterLastRun afterLastRun = meth.getAnnotation(AfterLastRun.class);
477         if (afterLastRun != null && benchAnno == null) {
478             returnVal = false;
479         }
480 
481         final AfterBenchClass afterClass = meth.getAnnotation(AfterBenchClass.class);
482         if (afterClass != null && benchAnno == null) {
483             returnVal = false;
484         }
485 
486         // if method is not annotated with Bench and class is not annotated with
487         // BenchClass, the method is never benchmarkable.
488 
489         final BenchClass classBenchAnno = meth.getDeclaringClass().getAnnotation(BenchClass.class);
490         if (benchAnno == null && classBenchAnno == null) {
491             returnVal = false;
492         }
493 
494         // check if method is executable for perfidix purposes.
495         if (!isReflectedExecutable(meth, Bench.class)) {
496             returnVal = false;
497         }
498         return returnVal;
499     }
500 
501     /**
502      * Checks if this method is executable via reflection for perfidix purposes.
503      * That means that the method has no parameters, no return-value, is
504      * non-static, is public and throws no exceptions.
505      * 
506      * @param meth
507      *            method to be checked
508      * @param anno
509      *            anno for method to be check, necessary since different
510      *            attributes are possible depending on the anno
511      * @return true if method matches requirements.
512      */
513     public static boolean isReflectedExecutable(final Method meth, final Class<? extends Annotation> anno) {
514         boolean returnVal = true;
515         // if method has parameters, the method is not benchmarkable
516         if (meth.getGenericParameterTypes().length > 0) {
517             returnVal = false;
518         }
519         // if method is static, the method is not benchmarkable
520         if (!(anno.equals(BeforeBenchClass.class)) && Modifier.isStatic(meth.getModifiers())) {
521             returnVal = false;
522         }
523         // if method is not public, the method is not benchmarkable
524         if (!Modifier.isPublic(meth.getModifiers())) {
525             returnVal = false;
526         }
527         // if method has another returnValue than void, the method is not
528         // benchmarkable
529         if (!meth.getGenericReturnType().equals(Void.TYPE)) {
530             returnVal = false;
531         }
532 
533         return returnVal;
534     }
535 
536     /** {@inheritDoc} */
537     @Override
538     public int hashCode() {
539         final int prime = 31;
540         int result = 1;
541         if (methodToBench == null) {
542             result = prime * result;
543         } else {
544             result = prime * result + methodToBench.hashCode();
545         }
546 
547         return result;
548     }
549 
550     /** {@inheritDoc} */
551     @Override
552     public boolean equals(final Object obj) {
553         boolean returnVal = true;
554         if (this == obj) {
555             returnVal = true;
556         }
557         if (obj == null) {
558             returnVal = false;
559         }
560         if (getClass() != obj.getClass()) {
561             returnVal = false;
562         }
563         final BenchmarkMethod other = (BenchmarkMethod)obj;
564         if (methodToBench == null) {
565             if (other.methodToBench != null) {
566                 returnVal = false;
567             }
568         } else {
569             if (!methodToBench.equals(other.methodToBench)) {
570                 returnVal = false;
571 
572             }
573         }
574         return returnVal;
575     }
576 
577     /**
578      * {@inheritDoc}
579      */
580     @Override
581     public String toString() {
582         return new StringBuilder(methodToBench.getName()).toString();
583     }
584 
585     /**
586      * This method returns the fully qualified name consisting of its own name
587      * and its class name
588      * 
589      * @return the {@link String} name von the bench method consisting of fully
590      *         qualified name of its class and its own name
591      */
592     public String getMethodWithClassName() {
593 
594         return new StringBuilder(methodToBench.getDeclaringClass().getName() + "." + methodToBench.getName())
595             .toString();
596     }
597 }