1/*
2 * Copyright (C) 2011 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.runner;
18
19import com.google.caliper.Param;
20import com.google.caliper.util.Parser;
21import com.google.caliper.util.Parsers;
22import com.google.caliper.util.Util;
23import com.google.common.collect.ImmutableCollection;
24import com.google.common.collect.ImmutableList;
25import com.google.common.collect.ImmutableSet;
26import com.google.common.primitives.Primitives;
27
28import java.lang.reflect.Field;
29import java.text.ParseException;
30
31/**
32 * Represents an injectable parameter, marked with one of @Param, @VmParam. Has nothing to do with
33 * particular choices of <i>values</i> for this parameter (except that it knows how to find the
34 * <i>default</i> values).
35 */
36public final class Parameter {
37  public static Parameter create(Field field) throws InvalidBenchmarkException {
38    return new Parameter(field);
39  }
40
41  private final Field field;
42  private final Parser<?> parser;
43  private final ImmutableList<String> defaults;
44
45  public Parameter(Field field) throws InvalidBenchmarkException {
46    if (Util.isStatic(field)) {
47      throw new InvalidBenchmarkException("Parameter field '%s' must not be static",
48          field.getName());
49    }
50    if (RESERVED_NAMES.contains(field.getName())) {
51      throw new InvalidBenchmarkException("Class '%s' uses reserved parameter name '%s'",
52          field.getDeclaringClass(), field.getName());
53    }
54
55    this.field = field;
56    field.setAccessible(true);
57
58    Class<?> type = Primitives.wrap(field.getType());
59    try {
60      this.parser = Parsers.conventionalParser(type);
61    } catch (NoSuchMethodException e) {
62      throw new InvalidBenchmarkException("Type '%s' of parameter field '%s' has no recognized "
63          + "String-converting method; see <TODO> for details", type, field.getName());
64    }
65
66    this.defaults = findDefaults(field);
67    validate(defaults);
68  }
69
70  void validate(ImmutableCollection<String> values) throws InvalidBenchmarkException {
71    for (String valueAsString : values) {
72      try {
73        parser.parse(valueAsString);
74      } catch (ParseException e) {
75        throw new InvalidBenchmarkException(
76            "Cannot convert value '%s' to type '%s': %s",
77            valueAsString, field.getType(), e.getMessage());
78      }
79    }
80  }
81
82  static final ImmutableSet<String> RESERVED_NAMES = ImmutableSet.of(
83      "benchmark",
84      "environment",
85      "instrument",
86      "measurement", // e.g. runtime, allocation, etc.
87      "run",
88      "trial", // currently unused, but we might need it
89      "vm");
90
91  String name() {
92    return field.getName();
93  }
94
95  ImmutableList<String> defaults() {
96    return defaults;
97  }
98
99  void inject(Object benchmark, String value) {
100    try {
101      Object o = parser.parse(value);
102      field.set(benchmark, o);
103    } catch (ParseException impossible) {
104      // already validated both defaults and command-line
105      throw new AssertionError(impossible);
106    } catch (IllegalAccessException impossible) {
107      throw new AssertionError(impossible);
108    }
109  }
110
111  private static ImmutableList<String> findDefaults(Field field) {
112    String[] defaultsAsStrings = field.getAnnotation(Param.class).value();
113    if (defaultsAsStrings.length > 0) {
114      return ImmutableList.copyOf(defaultsAsStrings);
115    }
116
117    Class<?> type = field.getType();
118    if (type == boolean.class) {
119      return ALL_BOOLEANS;
120    }
121
122    if (type.isEnum()) {
123      ImmutableList.Builder<String> builder = ImmutableList.builder();
124      for (Object enumConstant : type.getEnumConstants()) {
125        builder.add(enumConstant.toString());
126      }
127      return builder.build();
128    }
129    return ImmutableList.of();
130  }
131
132  private static final ImmutableList<String> ALL_BOOLEANS = ImmutableList.of("true", "false");
133}
134