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 }