aboutsummaryrefslogtreecommitdiff
path: root/src/com/google/caliper/Parameter.java
blob: caca2523ea6fc55aafeea638ff3f061fb86fc5f1 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
/*
 * 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.Field;
import java.lang.reflect.Member;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.EnumSet;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;

/**
 * A parameter in a {@link SimpleBenchmark}.
 */
abstract class Parameter<T> {

  private final Field field;

  private Parameter(Field field) {
    this.field = field;
  }

  /**
   * Returns all properties for the given class.
   */
  public static Map<String, Parameter<?>> forClass(Class<? extends Benchmark> suiteClass) {
    Map<String, Parameter<?>> parameters = new TreeMap<String, Parameter<?>>();
    for (Field field : suiteClass.getDeclaredFields()) {
      if (field.isAnnotationPresent(Param.class)) {
        field.setAccessible(true);
        Parameter<?> parameter = forField(suiteClass, field);
        parameters.put(parameter.getName(), parameter);
      }
    }
    return parameters;
  }

  private static Parameter<?> forField(
      Class<? extends Benchmark> suiteClass, final Field field) {
    // First check for String values on the annotation itself
    final Object[] defaults = field.getAnnotation(Param.class).value();
    if (defaults.length > 0) {
      return new Parameter<Object>(field) {
        @Override public Collection<Object> values() throws Exception {
          return Arrays.asList(defaults);
        }
      };
      // TODO: or should we continue so we can give an error/warning if params are also give in a
      // method or field?
    }

    Parameter<?> result = null;
    Type returnType = null;
    Member member = null;

    // Now check for a fooValues() method
    try {
      final Method valuesMethod = suiteClass.getDeclaredMethod(field.getName() + "Values");
      valuesMethod.setAccessible(true);
      member = valuesMethod;
      returnType = valuesMethod.getGenericReturnType();
      result = new Parameter<Object>(field) {
        @SuppressWarnings("unchecked") // guarded below
        @Override public Collection<Object> values() throws Exception {
          return (Collection<Object>) valuesMethod.invoke(null);
        }
      };
    } catch (NoSuchMethodException ignored) {
    }

    // Now check for a fooValues field
    try {
      final Field valuesField = suiteClass.getDeclaredField(field.getName() + "Values");
      valuesField.setAccessible(true);
      member = valuesField;
      if (result != null) {
        throw new ConfigurationException("Two values members defined for " + field);
      }
      returnType = valuesField.getGenericType();
      result = new Parameter<Object>(field) {
        @SuppressWarnings("unchecked") // guarded below
        @Override public Collection<Object> values() throws Exception {
          return (Collection<Object>) valuesField.get(null);
        }
      };
    } catch (NoSuchFieldException ignored) {
    }

    if (member != null && !Modifier.isStatic(member.getModifiers())) {
        throw new ConfigurationException("Values member must be static " + member);
    }

    // If there isn't a values member but the parameter is an enum, we default
    // to EnumSet.allOf.
    if (member == null && field.getType().isEnum()) {
      returnType = Collection.class;
      result = new Parameter<Object>(field) {
        // TODO: figure out the simplest way to make this compile and be green in IDEA too
        @SuppressWarnings({"unchecked", "RawUseOfParameterizedType", "RedundantCast"})
        // guarded above
        @Override public Collection<Object> values() throws Exception {
          Set<Enum> set = EnumSet.allOf((Class<Enum>) field.getType());
          return (Collection) set;
        }
      };
    }

    if (result == null) {
      return new Parameter<Object>(field) {
        @Override public Collection<Object> values() {
          // TODO: need tests to make sure this fails properly when no cmdline params given and
          // works properly when they are given
          return Collections.emptySet();
        }
      };
    } else if (!isValidReturnType(returnType)) {
      throw new ConfigurationException("Invalid return type " + returnType
          + " for values member " + member + "; must be Collection");
    }
    return result;
  }

  private static boolean isValidReturnType(Type returnType) {
    if (returnType == Collection.class) {
      return true;
    }
    if (returnType instanceof ParameterizedType) {
      ParameterizedType type = (ParameterizedType) returnType;
      if (type.getRawType() == Collection.class) {
        return true;
      }
    }
    return false;
  }

  /**
   * Sets the value of this property to the specified value for the given suite.
   */
  public void set(Benchmark suite, Object value) throws Exception {
    field.set(suite, value);
  }

  /**
   * Returns the available values of the property as specified by the suite.
   */
  public abstract Collection<T> values() throws Exception;

  /**
   * Returns the parameter's type, such as double.class.
   */
  public Type getType() {
    return field.getGenericType();
  }

  /**
   * Returns the field's name.
   */
  String getName() {
    return field.getName();
  }
}