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.util;
18
19import com.google.common.collect.ImmutableList;
20import com.google.common.primitives.Primitives;
21
22import java.lang.reflect.Constructor;
23import java.lang.reflect.InvocationTargetException;
24import java.lang.reflect.Method;
25import java.text.ParseException;
26import java.util.List;
27
28public class Parsers {
29  public static final Parser<String> IDENTITY = new Parser<String>() {
30    @Override public String parse(CharSequence in) {
31      return in.toString();
32    }
33  };
34
35  private static final List<String> CONVERSION_METHOD_NAMES =
36      ImmutableList.of("fromString", "decode", "valueOf");
37
38  /**
39   * Parser that tries, in this order:
40   * <ul>
41   * <li>ResultType.fromString(String)
42   * <li>ResultType.decode(String)
43   * <li>ResultType.valueOf(String)
44   * <li>new ResultType(String)
45   * </ul>
46   */
47  public static <T> Parser<T> conventionalParser(Class<T> resultType)
48      throws NoSuchMethodException {
49    if (resultType == String.class) {
50      @SuppressWarnings("unchecked") // T == String
51      Parser<T> identity = (Parser<T>) IDENTITY;
52      return identity;
53    }
54
55    final Class<T> wrappedResultType = Primitives.wrap(resultType);
56
57    for (String methodName : CONVERSION_METHOD_NAMES) {
58      try {
59        final Method method = wrappedResultType.getDeclaredMethod(methodName, String.class);
60
61        if (Util.isStatic(method) && wrappedResultType.isAssignableFrom(method.getReturnType())) {
62          method.setAccessible(true); // to permit inner enums, etc.
63          return new InvokingParser<T>() {
64            @Override protected T invoke(String input) throws Exception {
65              return wrappedResultType.cast(method.invoke(null, input));
66            }
67          };
68        }
69      } catch (Exception tryAgain) {
70      }
71    }
72
73    final Constructor<T> constr = wrappedResultType.getDeclaredConstructor(String.class);
74    constr.setAccessible(true);
75    return new InvokingParser<T>() {
76      @Override protected T invoke(String input) throws Exception {
77        return wrappedResultType.cast(constr.newInstance(input));
78      }
79    };
80  }
81
82  abstract static class InvokingParser<T> implements Parser<T> {
83    @Override public T parse(CharSequence input) throws ParseException {
84      try {
85        return invoke(input.toString());
86      } catch (InvocationTargetException e) {
87        Throwable cause = e.getCause();
88        String desc = firstNonNull(cause.getMessage(), cause.getClass().getSimpleName());
89        throw newParseException(desc, cause);
90      } catch (Exception e) {
91        throw newParseException("Unknown parsing problem", e);
92      }
93    }
94
95    protected abstract T invoke(String input) throws Exception;
96  }
97
98  public static ParseException newParseException(String message, Throwable cause) {
99    ParseException pe = newParseException(message);
100    pe.initCause(cause);
101    return pe;
102  }
103
104  public static ParseException newParseException(String message) {
105    return new ParseException(message, 0);
106  }
107
108  private static <T> T firstNonNull(T first, T second) {
109    return (first != null) ? first : second;
110  }
111}
112