blob: c79bc86a270dd5b173dfbb9a665702575c9cd347 [file] [log] [blame]
Paul Duffin7fc0b452015-11-10 17:45:15 +00001/*
2 * Copyright (C) 2009 Google Inc.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.google.caliper;
18
19import com.google.common.annotations.VisibleForTesting;
20
21import java.lang.reflect.Field;
22import java.lang.reflect.Member;
23import java.lang.reflect.Method;
24import java.lang.reflect.Modifier;
25import java.lang.reflect.ParameterizedType;
26import java.lang.reflect.Type;
27import java.util.Arrays;
28import java.util.Collection;
29import java.util.Collections;
30import java.util.EnumSet;
31import java.util.Map;
32import java.util.Set;
33import java.util.TreeMap;
34
35/**
36 * A parameter in a {@link SimpleBenchmark}.
37 *
38 * @param <T> the (possibly wrapped) type of the parameter field, such as {@link
39 * String} or {@link Integer}
40 */
41abstract class Parameter<T> {
42
43 private final Field field;
44
45 private Parameter(Field field) {
46 this.field = field;
47 }
48
49 /**
50 * Returns all parameters for the given class.
51 */
52 public static Map<String, Parameter<?>> forClass(Class<? extends Benchmark> suiteClass) {
53 Map<String, Parameter<?>> parameters = new TreeMap<String, Parameter<?>>();
54 for (Field field : suiteClass.getDeclaredFields()) {
55 if (field.isAnnotationPresent(Param.class)) {
56 field.setAccessible(true);
57 Parameter<?> parameter = forField(suiteClass, field);
58 parameters.put(parameter.getName(), parameter);
59 }
60 }
61 return parameters;
62 }
63
64 @VisibleForTesting
65 static Parameter<?> forField(
66 Class<? extends Benchmark> suiteClass, final Field field) {
67 // First check for String values on the annotation itself
68 final Object[] defaults = field.getAnnotation(Param.class).value();
69 if (defaults.length > 0) {
70 return new Parameter<Object>(field) {
71 @Override public Iterable<Object> values() throws Exception {
72 return Arrays.asList(defaults);
73 }
74 };
75 // TODO: or should we continue so we can give an error/warning if params are also give in a
76 // method or field?
77 }
78
79 Parameter<?> result = null;
80 Type returnType = null;
81 Member member = null;
82
83 // Now check for a fooValues() method
84 try {
85 final Method valuesMethod = suiteClass.getDeclaredMethod(field.getName() + "Values");
86 if (!Modifier.isStatic(valuesMethod.getModifiers())) {
87 throw new ConfigurationException("Values method must be static " + member);
88 }
89 valuesMethod.setAccessible(true);
90 member = valuesMethod;
91 returnType = valuesMethod.getGenericReturnType();
92 result = new Parameter<Object>(field) {
93 @SuppressWarnings("unchecked") // guarded below
94 @Override public Iterable<Object> values() throws Exception {
95 return (Iterable<Object>) valuesMethod.invoke(null);
96 }
97 };
98 } catch (NoSuchMethodException ignored) {
99 }
100
101 // Now check for a fooValues field
102 try {
103 final Field valuesField = suiteClass.getDeclaredField(field.getName() + "Values");
104 if (!Modifier.isStatic(valuesField.getModifiers())) {
105 throw new ConfigurationException("Values field must be static " + member);
106 }
107 valuesField.setAccessible(true);
108 member = valuesField;
109 if (result != null) {
110 throw new ConfigurationException("Two values members defined for " + field);
111 }
112 returnType = valuesField.getGenericType();
113 result = new Parameter<Object>(field) {
114 @SuppressWarnings("unchecked") // guarded below
115 @Override public Iterable<Object> values() throws Exception {
116 return (Iterable<Object>) valuesField.get(null);
117 }
118 };
119 } catch (NoSuchFieldException ignored) {
120 }
121
122 // If there isn't a values member but the parameter is an enum, we default
123 // to EnumSet.allOf.
124 if (member == null && field.getType().isEnum()) {
125 returnType = Collection.class;
126 result = new Parameter<Object>(field) {
127 // TODO: figure out the simplest way to make this compile and be green in IDEA too
128 @SuppressWarnings({"unchecked", "RawUseOfParameterizedType", "RedundantCast"})
129 // guarded above
130 @Override public Iterable<Object> values() throws Exception {
131 Set<Enum> set = EnumSet.allOf((Class<Enum>) field.getType());
132 return Collections.<Object>unmodifiableSet(set);
133 }
134 };
135 }
136
137 // If it's boolean, default to (true, false)
138 if (member == null && field.getType() == boolean.class) {
139 returnType = Collection.class;
140 result = new Parameter<Object>(field) {
141 @Override public Iterable<Object> values() throws Exception {
142 return Arrays.<Object>asList(Boolean.TRUE, Boolean.FALSE);
143 }
144 };
145 }
146
147 if (result == null) {
148 return new Parameter<Object>(field) {
149 @Override public Iterable<Object> values() {
150 // TODO: need tests to make sure this fails properly when no cmdline params given and
151 // works properly when they are given. Also, can we restructure the code so that we
152 // just throw here instead of later?
153 return Collections.emptySet();
154 }
155 };
156 } else if (!isValidReturnType(returnType)) {
157 throw new ConfigurationException("Invalid return type " + returnType
158 + " for values member " + member + "; must be Collection");
159 }
160 return result;
161 }
162
163 private static boolean isValidReturnType(Type type) {
164 if (type instanceof Class) {
165 return isIterableClass(type);
166 }
167 if (type instanceof ParameterizedType) {
168 return isIterableClass(((ParameterizedType) type).getRawType());
169 }
170 return false;
171 }
172
173 private static boolean isIterableClass(Type returnClass) {
174 return Iterable.class.isAssignableFrom((Class<?>) returnClass);
175 }
176
177 /**
178 * Sets the value of this property to the specified value for the given suite.
179 */
180 public void set(Benchmark suite, Object value) throws Exception {
181 field.set(suite, value);
182 }
183
184 /**
185 * Returns the available values of the property as specified by the suite.
186 */
187 public abstract Iterable<T> values() throws Exception;
188
189 /**
190 * Returns the parameter's type, such as double.class.
191 */
192 public Type getType() {
193 return field.getGenericType();
194 }
195
196 /**
197 * Returns the field's name.
198 */
199 String getName() {
200 return field.getName();
201 }
202}