blob: 0e197f67394c1ee25e6c927474d8e1bc2f6d3e48 [file] [log] [blame]
/*
* Copyright (C) 2009 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.caliper;
import java.lang.reflect.Constructor;
import java.lang.reflect.Type;
import java.util.*;
/**
* A convenience class for implementing benchmark suites in plain code.
* Implementing classes must have a no-arguments constructor.
*
* <h3>Benchmarks</h3>
* The benchmarks of a suite are defined by inner classes within the suite.
* These inner classes implement the {@link Benchmark} interface. They may be
* static. They are not permitted to take parameters in their constructors.
*
* <h3>Parameters</h3>
* Implementing classes may be configured using parameters. Each parameter is a
* property of a benchmark, plus the default values that fulfill it. Parameters
* are specified by annotated fields:
* <pre>
* {@literal @}Param int length;
* </pre>
* The available values for a parameter are specified by another field with the
* same name plus the {@code Values} suffix. The type of this field must be an
* {@code Iterable} of the parameter's type.
* <pre>
* Iterable&lt;Integer&gt; lengthValues = Arrays.asList(10, 100, 1000, 10000);
* </pre>
* Alternatively, the available values may be specified with a method. The
* method's name follows the same naming convention and returns the same type.
* Such methods may not accept parameters of their own.
* <pre>
* Iterable&lt;Integer&gt; lengthValues() {
* return Arrays.asList(10, 100, 1000, 10000);
* }
* </pre>
*/
public abstract class DefaultBenchmarkSuite extends BenchmarkSuite {
private final Map<String, Parameter<?>> parameters;
private final Map<Class<? extends Benchmark>, BenchmarkFactory> benchmarkFactories;
protected void setUp() throws Exception {}
protected DefaultBenchmarkSuite() {
parameters = Parameter.forClass(getClass());
benchmarkFactories = createBenchmarkFactories();
if (benchmarkFactories.isEmpty()) {
throw new ConfigurationException(
"No benchmarks defined in " + getClass().getName());
}
}
protected Set<Class<? extends Benchmark>> benchmarkClasses() {
return benchmarkFactories.keySet();
}
protected Set<String> parameterNames() {
return parameters.keySet();
}
protected Set<String> parameterValues(String parameterName) {
try {
TypeConverter typeConverter = new TypeConverter();
Parameter<?> parameter = parameters.get(parameterName);
if (parameter == null) {
throw new IllegalArgumentException();
}
Collection<?> values = parameter.values();
Type type = parameter.getType();
Set<String> result = new LinkedHashSet<String>();
for (Object value : values) {
result.add(typeConverter.toString(value, type));
}
return result;
} catch (Exception e) {
throw new ExecutionException(e);
}
}
protected Benchmark createBenchmark(Class<? extends Benchmark> benchmarkClass,
Map<String, String> parameterValues) {
TypeConverter typeConverter = new TypeConverter();
BenchmarkFactory benchmarkFactory = benchmarkFactories.get(benchmarkClass);
if (benchmarkFactory == null) {
throw new IllegalArgumentException();
}
if (!parameters.keySet().equals(parameterValues.keySet())) {
throw new IllegalArgumentException("Invalid parameters specified. Expected "
+ parameters.keySet() + " but was " + parameterValues.keySet());
}
try {
DefaultBenchmarkSuite copyOfSelf = getClass().newInstance();
Benchmark benchmark = benchmarkFactory.create(copyOfSelf);
for (Map.Entry<String, String> entry : parameterValues.entrySet()) {
Parameter parameter = parameters.get(entry.getKey());
Object value = typeConverter.fromString(entry.getValue(), parameter.getType());
parameter.set(copyOfSelf, value);
}
copyOfSelf.setUp();
return benchmark;
} catch (Exception e) {
throw new ExecutionException(e);
}
}
/**
* Returns a spec for each benchmark defined in the specified class. The
* returned specs have no parameter values; those must be added separately.
*/
private Map<Class<? extends Benchmark>, BenchmarkFactory> createBenchmarkFactories() {
Map<Class<? extends Benchmark>, BenchmarkFactory> result
= new LinkedHashMap<Class<? extends Benchmark>, BenchmarkFactory>();
for (Class<?> c : getClass().getDeclaredClasses()) {
if (!Benchmark.class.isAssignableFrom(c) || c.isInterface()) {
continue;
}
@SuppressWarnings("unchecked") // guarded by isAssignableFrom
Class<? extends Benchmark> benchmarkClass = (Class<? extends Benchmark>) c;
try {
final Constructor<? extends Benchmark> constructor
= benchmarkClass.getDeclaredConstructor();
constructor.setAccessible(true);
result.put(benchmarkClass, new BenchmarkFactory() {
public Benchmark create(BenchmarkSuite suite) throws Exception {
return constructor.newInstance();
}
});
continue;
} catch (NoSuchMethodException ignored) {
}
try {
final Constructor<? extends Benchmark> constructor
= benchmarkClass.getDeclaredConstructor(getClass());
constructor.setAccessible(true);
result.put(benchmarkClass, new BenchmarkFactory() {
public Benchmark create(BenchmarkSuite suite) throws Exception {
return constructor.newInstance(suite);
}
});
continue;
} catch (NoSuchMethodException ignored) {
}
throw new ConfigurationException("No usable constructor for "
+ benchmarkClass.getName() + "\n Benchmarks may only use no arguments constructors.");
}
return result;
}
interface BenchmarkFactory {
Benchmark create(BenchmarkSuite suite) throws Exception;
}
}